diff options
author | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2016-04-04 15:04:44 +0200 |
---|---|---|
committer | Hanspeter Portner <dev@open-music-kontrollers.ch> | 2016-04-04 15:04:44 +0200 |
commit | bc9f0c0de0698096b949956951afd28c5ceb623d (patch) | |
tree | ae01d9d694c10ad743a142ad50182721c350adfd /sandbox_ui.lv2 | |
parent | 83ad99f529ed95cae172594561371dd5c4fb7d66 (diff) | |
parent | 9c4e34bb484da8b9e44b8ac5147dc1330f283a11 (diff) | |
download | sherlock.lv2-bc9f0c0de0698096b949956951afd28c5ceb623d.tar.xz (sig) |
Add 'sandbox_ui.lv2/' from commit '9c4e34bb484da8b9e44b8ac5147dc1330f283a11'0.1.1
git-subtree-dir: sandbox_ui.lv2
git-subtree-mainline: 83ad99f529ed95cae172594561371dd5c4fb7d66
git-subtree-split: 9c4e34bb484da8b9e44b8ac5147dc1330f283a11
Diffstat (limited to 'sandbox_ui.lv2')
-rw-r--r-- | sandbox_ui.lv2/COPYING | 201 | ||||
-rw-r--r-- | sandbox_ui.lv2/README.md | 18 | ||||
-rw-r--r-- | sandbox_ui.lv2/lv2_external_ui.h | 109 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_efl.c | 192 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_io.h | 552 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_master.c | 100 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_master.h | 65 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_slave.c | 563 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_slave.h | 71 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_ui.c | 378 | ||||
-rw-r--r-- | sandbox_ui.lv2/sandbox_ui.h | 47 |
11 files changed, 2296 insertions, 0 deletions
diff --git a/sandbox_ui.lv2/COPYING b/sandbox_ui.lv2/COPYING new file mode 100644 index 0000000..ddb9a46 --- /dev/null +++ b/sandbox_ui.lv2/COPYING @@ -0,0 +1,201 @@ + The Artistic License 2.0 + + Copyright (c) 2000-2006, The Perl Foundation. + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +Preamble + +This license establishes the terms under which a given free software +Package may be copied, modified, distributed, and/or redistributed. +The intent is that the Copyright Holder maintains some artistic +control over the development of that Package while still keeping the +Package available as open source and free software. + +You are always permitted to make arrangements wholly outside of this +license directly with the Copyright Holder of a given Package. If the +terms of this license do not permit the full use that you propose to +make of the Package, you should contact the Copyright Holder and seek +a different licensing arrangement. + +Definitions + + "Copyright Holder" means the individual(s) or organization(s) + named in the copyright notice for the entire Package. + + "Contributor" means any party that has contributed code or other + material to the Package, in accordance with the Copyright Holder's + procedures. + + "You" and "your" means any person who would like to copy, + distribute, or modify the Package. + + "Package" means the collection of files distributed by the + Copyright Holder, and derivatives of that collection and/or of + those files. A given Package may consist of either the Standard + Version, or a Modified Version. + + "Distribute" means providing a copy of the Package or making it + accessible to anyone else, or in the case of a company or + organization, to others outside of your company or organization. + + "Distributor Fee" means any fee that you charge for Distributing + this Package or providing support for this Package to another + party. It does not mean licensing fees. + + "Standard Version" refers to the Package if it has not been + modified, or has been modified only in ways explicitly requested + by the Copyright Holder. + + "Modified Version" means the Package, if it has been changed, and + such changes were not explicitly requested by the Copyright + Holder. + + "Original License" means this Artistic License as Distributed with + the Standard Version of the Package, in its current version or as + it may be modified by The Perl Foundation in the future. + + "Source" form means the source code, documentation source, and + configuration files for the Package. + + "Compiled" form means the compiled bytecode, object code, binary, + or any other form resulting from mechanical transformation or + translation of the Source form. + + +Permission for Use and Modification Without Distribution + +(1) You are permitted to use the Standard Version and create and use +Modified Versions for any purpose without restriction, provided that +you do not Distribute the Modified Version. + + +Permissions for Redistribution of the Standard Version + +(2) You may Distribute verbatim copies of the Source form of the +Standard Version of this Package in any medium without restriction, +either gratis or for a Distributor Fee, provided that you duplicate +all of the original copyright notices and associated disclaimers. At +your discretion, such verbatim copies may or may not include a +Compiled form of the Package. + +(3) You may apply any bug fixes, portability changes, and other +modifications made available from the Copyright Holder. The resulting +Package will still be considered the Standard Version, and as such +will be subject to the Original License. + + +Distribution of Modified Versions of the Package as Source + +(4) You may Distribute your Modified Version as Source (either gratis +or for a Distributor Fee, and with or without a Compiled form of the +Modified Version) provided that you clearly document how it differs +from the Standard Version, including, but not limited to, documenting +any non-standard features, executables, or modules, and provided that +you do at least ONE of the following: + + (a) make the Modified Version available to the Copyright Holder + of the Standard Version, under the Original License, so that the + Copyright Holder may include your modifications in the Standard + Version. + + (b) ensure that installation of your Modified Version does not + prevent the user installing or running the Standard Version. In + addition, the Modified Version must bear a name that is different + from the name of the Standard Version. + + (c) allow anyone who receives a copy of the Modified Version to + make the Source form of the Modified Version available to others + under + + (i) the Original License or + + (ii) a license that permits the licensee to freely copy, + modify and redistribute the Modified Version using the same + licensing terms that apply to the copy that the licensee + received, and requires that the Source form of the Modified + Version, and of any works derived from it, be made freely + available in that license fees are prohibited but Distributor + Fees are allowed. + + +Distribution of Compiled Forms of the Standard Version +or Modified Versions without the Source + +(5) You may Distribute Compiled forms of the Standard Version without +the Source, provided that you include complete instructions on how to +get the Source of the Standard Version. Such instructions must be +valid at the time of your distribution. If these instructions, at any +time while you are carrying out such distribution, become invalid, you +must provide new instructions on demand or cease further distribution. +If you provide valid instructions or cease distribution within thirty +days after you become aware that the instructions are invalid, then +you do not forfeit any of your rights under this license. + +(6) You may Distribute a Modified Version in Compiled form without +the Source, provided that you comply with Section 4 with respect to +the Source of the Modified Version. + + +Aggregating or Linking the Package + +(7) You may aggregate the Package (either the Standard Version or +Modified Version) with other packages and Distribute the resulting +aggregation provided that you do not charge a licensing fee for the +Package. Distributor Fees are permitted, and licensing fees for other +components in the aggregation are permitted. The terms of this license +apply to the use and Distribution of the Standard or Modified Versions +as included in the aggregation. + +(8) You are permitted to link Modified and Standard Versions with +other works, to embed the Package in a larger work of your own, or to +build stand-alone binary or bytecode versions of applications that +include the Package, and Distribute the result without restriction, +provided the result does not expose a direct interface to the Package. + + +Items That are Not Considered Part of a Modified Version + +(9) Works (including, but not limited to, modules and scripts) that +merely extend or make use of the Package, do not, by themselves, cause +the Package to be a Modified Version. In addition, such works are not +considered parts of the Package itself, and are not subject to the +terms of this license. + + +General Provisions + +(10) Any use, modification, and distribution of the Standard or +Modified Versions is governed by this Artistic License. By using, +modifying or distributing the Package, you accept this license. Do not +use, modify, or distribute the Package, if you do not accept this +license. + +(11) If your Modified Version has been derived from a Modified +Version made by someone other than you, you are nevertheless required +to ensure that your Modified Version complies with the requirements of +this license. + +(12) This license does not grant you the right to use any trademark, +service mark, tradename, or logo of the Copyright Holder. + +(13) This license includes the non-exclusive, worldwide, +free-of-charge patent license to make, have made, use, offer to sell, +sell, import and otherwise transfer the Package with respect to any +patent claims licensable by the Copyright Holder that are necessarily +infringed by the Package. If you institute patent litigation +(including a cross-claim or counterclaim) against any party alleging +that the Package constitutes direct or contributory patent +infringement, then this Artistic License to you shall terminate on the +date that such litigation is filed. + +(14) Disclaimer of Warranty: +THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS +IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR +NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL +LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/sandbox_ui.lv2/README.md b/sandbox_ui.lv2/README.md new file mode 100644 index 0000000..d9f1d9f --- /dev/null +++ b/sandbox_ui.lv2/README.md @@ -0,0 +1,18 @@ +# LV2 sandboxed UI + +### License + +Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch) + +This is free software: you can redistribute it and/or modify +it under the terms of the Artistic License 2.0 as published by +The Perl Foundation. + +This source is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +Artistic License 2.0 for more details. + +You should have received a copy of the Artistic License 2.0 +along the source as a COPYING file. If not, obtain it from +<http://www.perlfoundation.org/artistic_license_2_0>. diff --git a/sandbox_ui.lv2/lv2_external_ui.h b/sandbox_ui.lv2/lv2_external_ui.h new file mode 100644 index 0000000..2c9e6ee --- /dev/null +++ b/sandbox_ui.lv2/lv2_external_ui.h @@ -0,0 +1,109 @@ +/* + LV2 External UI extension + This work is in public domain. + + This file is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + If you have questions, contact Filipe Coelho (aka falkTX) <falktx@falktx.com> + or ask in #lad channel, FreeNode IRC network. +*/ + +/** + @file lv2_external_ui.h + C header for the LV2 External UI extension <http://kxstudio.sf.net/ns/lv2ext/external-ui>. +*/ + +#ifndef LV2_EXTERNAL_UI_H +#define LV2_EXTERNAL_UI_H + +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#define LV2_EXTERNAL_UI_URI "http://kxstudio.sf.net/ns/lv2ext/external-ui" +#define LV2_EXTERNAL_UI_PREFIX LV2_EXTERNAL_UI_URI "#" + +#define LV2_EXTERNAL_UI__Host LV2_EXTERNAL_UI_PREFIX "Host" +#define LV2_EXTERNAL_UI__Widget LV2_EXTERNAL_UI_PREFIX "Widget" + +/** This extension used to be defined by a lv2plug.in URI */ +#define LV2_EXTERNAL_UI_DEPRECATED_URI "http://lv2plug.in/ns/extensions/ui#external" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * When LV2_EXTERNAL_UI__Widget UI is instantiated, the returned + * LV2UI_Widget handle must be cast to pointer to LV2_External_UI_Widget. + * UI is created in invisible state. + */ +typedef struct _LV2_External_UI_Widget { + /** + * Host calls this function regulary. UI library implementing the + * callback may do IPC or redraw the UI. + * + * @param _this_ the UI context + */ + void (*run)(struct _LV2_External_UI_Widget * _this_); + + /** + * Host calls this function to make the plugin UI visible. + * + * @param _this_ the UI context + */ + void (*show)(struct _LV2_External_UI_Widget * _this_); + + /** + * Host calls this function to make the plugin UI invisible again. + * + * @param _this_ the UI context + */ + void (*hide)(struct _LV2_External_UI_Widget * _this_); + +} LV2_External_UI_Widget; + +#define LV2_EXTERNAL_UI_RUN(ptr) (ptr)->run(ptr) +#define LV2_EXTERNAL_UI_SHOW(ptr) (ptr)->show(ptr) +#define LV2_EXTERNAL_UI_HIDE(ptr) (ptr)->hide(ptr) + +/** + * On UI instantiation, host must supply LV2_EXTERNAL_UI__Host feature. + * LV2_Feature::data must be pointer to LV2_External_UI_Host. + */ +typedef struct _LV2_External_UI_Host { + /** + * Callback that plugin UI will call when UI (GUI window) is closed by user. + * This callback will be called during execution of LV2_External_UI_Widget::run() + * (i.e. not from background thread). + * + * After this callback is called, UI is defunct. Host must call LV2UI_Descriptor::cleanup(). + * If host wants to make the UI visible again, the UI must be reinstantiated. + * + * @note When using the depreated URI LV2_EXTERNAL_UI_DEPRECATED_URI, + * some hosts will not call LV2UI_Descriptor::cleanup() as they should, + * and may call show() again without re-initialization. + * + * @param controller Host context associated with plugin UI, as + * supplied to LV2UI_Descriptor::instantiate(). + */ + void (*ui_closed)(LV2UI_Controller controller); + + /** + * Optional (may be NULL) "user friendly" identifier which the UI + * may display to allow a user to easily associate this particular + * UI instance with the correct plugin instance as it is represented + * by the host (e.g. "track 1" or "channel 4"). + * + * If supplied by host, the string will be referenced only during + * LV2UI_Descriptor::instantiate() + */ + const char * plugin_human_id; + +} LV2_External_UI_Host; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV2_EXTERNAL_UI_H */ diff --git a/sandbox_ui.lv2/sandbox_efl.c b/sandbox_ui.lv2/sandbox_efl.c new file mode 100644 index 0000000..90211de --- /dev/null +++ b/sandbox_ui.lv2/sandbox_efl.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sandbox_slave.h> + +#include <Elementary.h> + +typedef struct _app_t app_t; + +struct _app_t { + sandbox_slave_t *sb; + + Evas_Object *win; + Evas_Object *bg; + Evas_Object *widget; + Ecore_Fd_Handler *fd; +}; + +static Eina_Bool +_recv(void *data, Ecore_Fd_Handler *fd_handler) +{ + sandbox_slave_t *sb = data; + + sandbox_slave_recv(sb); + + return ECORE_CALLBACK_RENEW; +} + +static void +_del_request(void *data, Evas_Object *obj, void *event_info) +{ + app_t *app = data; + + elm_exit(); + app->bg = NULL; + app->win = NULL; +} + +static inline int +_init(sandbox_slave_t *sb, void *data) +{ + app_t *app= data; + + int w = 640; + int h = 360; + + const char *title = sandbox_slave_title_get(sb); + app->win = elm_win_add(NULL, title, ELM_WIN_BASIC); + if(!app->win) + { + fprintf(stderr, "elm_win_add failed\n"); + goto fail; + } + elm_win_title_set(app->win, title); + elm_win_autodel_set(app->win, EINA_TRUE); + evas_object_smart_callback_add(app->win, "delete,request", _del_request, app); + + app->bg = elm_bg_add(app->win); + if(!app->bg) + { + fprintf(stderr, "elm_bg_add failed\n"); + goto fail; + } + elm_bg_color_set(app->bg, 64, 64, 64); + evas_object_size_hint_weight_set(app->bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(app->bg, EVAS_HINT_FILL, EVAS_HINT_FILL); + evas_object_show(app->bg); + elm_win_resize_object_add(app->win, app->bg); + + if( sandbox_slave_instantiate(sb, (void *)app->win, (void *)&app->widget) + || !app->widget) + { + fprintf(stderr, "sandbox_slave_instantiate failed\n"); + goto fail; + } + evas_object_size_hint_weight_set(app->widget, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND); + evas_object_size_hint_align_set(app->widget, EVAS_HINT_FILL, EVAS_HINT_FILL); + + // get widget size hint + int W, H; + evas_object_size_hint_min_get(app->widget, &W, &H); + if(W != 0) + w = W; + if(H != 0) + h = H; + evas_object_show(app->widget); + elm_win_resize_object_add(app->win, app->widget); + + evas_object_resize(app->win, w, h); + evas_object_show(app->win); + + int fd; + sandbox_slave_fd_get(sb, &fd); + if(fd == -1) + { + fprintf(stderr, "sandbox_slave_instantiate failed\n"); + goto fail; + } + + app->fd= ecore_main_fd_handler_add(fd, ECORE_FD_READ, + _recv, sb, NULL, NULL); + if(!app->fd) + { + fprintf(stderr, "ecore_main_fd_handler_add failed\n"); + goto fail; + } + + return 0; + +fail: + return -1; +} + +static inline void +_run(sandbox_slave_t *sb, void *data) +{ + app_t *app = data; + + elm_run(); +} + +static inline void +_deinit(void *data) +{ + app_t *app = data; + + if(app->fd) + ecore_main_fd_handler_del(app->fd); + + if(app->bg) + { + elm_win_resize_object_del(app->win, app->bg); + evas_object_hide(app->bg); + evas_object_del(app->bg); + } + + if(app->win) + { + evas_object_hide(app->win); + evas_object_del(app->win); + } +} + +static const sandbox_slave_driver_t driver = { + .init_cb = _init, + .run_cb = _run, + .deinit_cb = _deinit, + .resize_cb = NULL +}; + +static int +elm_main(int argc, char **argv) +{ + static app_t app; + +#ifdef ELM_1_10 + elm_config_accel_preference_set("gl"); +#endif + + app.sb = sandbox_slave_new(argc, argv, &driver, &app); + if(app.sb) + { + sandbox_slave_run(app.sb); + sandbox_slave_free(app.sb); + printf("bye from %s\n", argv[0]); + return 0; + } + + printf("fail from %s\n", argv[0]); + return -1; +} + +ELM_MAIN(); diff --git a/sandbox_ui.lv2/sandbox_io.h b/sandbox_ui.lv2/sandbox_io.h new file mode 100644 index 0000000..1fca98e --- /dev/null +++ b/sandbox_ui.lv2/sandbox_io.h @@ -0,0 +1,552 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_IO_H +#define _SANDBOX_IO_H + +#include <sratom/sratom.h> + +#include <nanomsg/nn.h> +#include <nanomsg/pair.h> + +#include <lv2/lv2plug.in/ns/lv2core/lv2.h> +#include <lv2/lv2plug.in/ns/ext/urid/urid.h> +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define RDF_PREFIX "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0 +#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \ + for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \ + !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \ + (iter) = lv2_atom_tuple_next(iter)) + +typedef struct _atom_ser_t atom_ser_t; +typedef struct _sandbox_io_subscription_t sandbox_io_subscription_t; +typedef struct _sandbox_io_t sandbox_io_t; + +typedef void (*_sandbox_io_recv_cb_t)(void *data, uint32_t index, uint32_t size, + uint32_t format, const void *buf); +typedef void (*_sandbox_io_subscribe_cb_t)(void *data, uint32_t index, + uint32_t protocol, bool state); + +struct _atom_ser_t { + uint32_t size; + uint8_t *buf; + uint32_t offset; +}; + +struct _sandbox_io_subscription_t { + uint32_t protocol; + int32_t state; +}; + +struct _sandbox_io_t { + LV2_URID_Map *map; + LV2_URID_Unmap *unmap; + + int sock; + int id; + + Sratom *sratom; + atom_ser_t ser; + LV2_Atom_Forge_Frame frame; + LV2_Atom_Forge forge; + + const char *base_uri; + SerdNode subject; + SerdNode predicate; + + LV2_URID float_protocol; + LV2_URID peak_protocol; + LV2_URID event_transfer; + LV2_URID atom_transfer; + LV2_URID core_index; + LV2_URID rdf_value; + LV2_URID ui_protocol; + LV2_URID ui_period_start; + LV2_URID ui_period_size; + LV2_URID ui_peak; + LV2_URID ui_window_title; + LV2_URID ui_port_subscribe; +}; + +static inline LV2_Atom_Forge_Ref +_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, uint32_t size) +{ + atom_ser_t *ser = handle; + + const LV2_Atom_Forge_Ref ref = ser->offset + 1; + + const uint32_t new_offset = ser->offset + size; + if(new_offset > ser->size) + { + uint32_t new_size = ser->size << 1; + while(new_offset > new_size) + new_size <<= 1; + + if(!(ser->buf = realloc(ser->buf, new_size))) + return 0; // realloc failed + + ser->size = new_size; + } + + memcpy(ser->buf + ser->offset, buf, size); + ser->offset = new_offset; + + return ref; +} + +static inline LV2_Atom * +_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref) +{ + atom_ser_t *ser = handle; + + const uint32_t offset = ref - 1; + + return (LV2_Atom *)(ser->buf + offset); +} + +static inline void +_sandbox_io_reset(sandbox_io_t *io) +{ + atom_ser_t *ser = &io->ser; + ser->offset = 0; //TODO free? + + lv2_atom_forge_set_sink(&io->forge, _sink, _deref, ser); + lv2_atom_forge_tuple(&io->forge, &io->frame); +} + +static inline void +_sandbox_io_recv(sandbox_io_t *io, _sandbox_io_recv_cb_t recv_cb, + _sandbox_io_subscribe_cb_t subscribe_cb, void *data) +{ + char *ttl = NULL; + int res; + + while((res = nn_recv(io->sock, &ttl, NN_MSG, NN_DONTWAIT)) != -1) + { + //printf("%s\n\n", ttl); + LV2_Atom *atom = sratom_from_turtle(io->sratom, io->base_uri, + &io->subject, &io->predicate, ttl); + if(atom) + { + LV2_ATOM_TUPLE_FOREACH((LV2_Atom_Tuple *)atom, itm) + { + LV2_Atom_Object *obj = (LV2_Atom_Object *)itm; + + if(!lv2_atom_forge_is_object_type(&io->forge, obj->atom.type)) + continue; + + if(obj->body.otype == io->float_protocol) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom_Float *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value && (value->atom.type == io->forge.Float) + && (value->atom.size == sizeof(float)) ) + { + recv_cb(data, index->body, + sizeof(float), io->float_protocol, &value->body); + recv_cb(data, index->body, + sizeof(float), 0, &value->body); + } + } + else if(obj->body.otype == io->peak_protocol) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom_Int *period_start = NULL; + const LV2_Atom_Int *period_size = NULL; + const LV2_Atom_Float *peak= NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->ui_period_start, (const LV2_Atom **)&period_start}, + {io->ui_period_size, (const LV2_Atom **)&period_size}, + {io->ui_peak, (const LV2_Atom **)&peak}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && period_start && (period_start->atom.type == io->forge.Int) + && (period_start->atom.size == sizeof(int32_t)) + && period_size && (period_size->atom.type == io->forge.Int) + && (period_size->atom.size == sizeof(int32_t)) + && peak && (peak->atom.type == io->forge.Float) + && (peak->atom.size == sizeof(float)) ) + { + const LV2UI_Peak_Data peak_data = { + .period_start = period_start->body, + .period_size = period_size->body, + .peak = peak->body + }; + recv_cb(data, index->body, + sizeof(LV2UI_Peak_Data), io->peak_protocol, &peak_data); + } + } + else if(obj->body.otype == io->event_transfer) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value) + { + recv_cb(data, index->body, + lv2_atom_total_size(value), io->event_transfer, value); + } + } + else if(obj->body.otype == io->atom_transfer) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value) + { + recv_cb(data, index->body, + lv2_atom_total_size(value), io->atom_transfer, value); + } + } + else if(obj->body.otype == io->ui_port_subscribe) + { + const LV2_Atom_Int *index = NULL; + const LV2_Atom_URID *protocol = NULL; + const LV2_Atom_Bool *value = NULL; + LV2_Atom_Object_Query q [] = { + {io->core_index, (const LV2_Atom **)&index}, + {io->ui_protocol, (const LV2_Atom **)&protocol}, + {io->rdf_value, (const LV2_Atom **)&value}, + {0, NULL} + }; + lv2_atom_object_query(obj, q); + + if( index && (index->atom.type == io->forge.Int) + && value && (value->atom.type == io->forge.Bool) + && protocol && (protocol->atom.type == io->forge.URID)) + { + if(subscribe_cb) + subscribe_cb(data, index->body, protocol->body, value->body); + } + } + } + + free(atom); + } + + nn_freemsg(ttl); + } + + switch(nn_errno()) + { + case EAGAIN: + // do nothing + break; + case ETERM: + //FIXME done + // fall-through + default: + fprintf(stderr, "nn_recv: %s\n", nn_strerror(nn_errno())); + break; + } +} + +static inline bool +_sandbox_io_flush(sandbox_io_t *io) +{ + const LV2_Atom *atom = (const LV2_Atom *)io->ser.buf; + + bool more = false; + if( (io->ser.offset == 0) || (atom->size == 0) ) + return more; // empty tuple + + char *ttl = sratom_to_turtle(io->sratom, io->unmap, + io->base_uri, &io->subject, &io->predicate, + atom->type, atom->size, LV2_ATOM_BODY_CONST(atom)); + + if(ttl) + { + const size_t len = strlen(ttl) + 1; + //printf("sending: %zu\n\n%s\n\n", len, ttl); + + if(nn_send(io->sock, ttl, len, NN_DONTWAIT) == -1) + { + switch(nn_errno()) + { + case EAGAIN: + more = true; + break; + case ETERM: + //FIXME done + // fall-through + default: + fprintf(stderr, "nn_send: %s\n", nn_strerror(nn_errno())); + break; + } + } + else + { + _sandbox_io_reset(io); + } + + free(ttl); + } + + return more; +} + +static inline void +_sandbox_io_clean(LV2_Atom_Forge *forge, LV2_Atom *atom) +{ + if(atom->type == forge->Object) + { + LV2_Atom_Object *obj = (LV2_Atom_Object *)atom; + + if(obj->body.id != 0) + obj->body.id = 0; // if not, sratom will fail + + LV2_ATOM_OBJECT_FOREACH(obj, prop) + { + _sandbox_io_clean(forge, &prop->value); + } + } + else if(atom->type == forge->Tuple) + { + LV2_Atom_Tuple *tup = (LV2_Atom_Tuple *)atom; + + LV2_ATOM_TUPLE_FOREACH(tup, itm) + { + _sandbox_io_clean(forge, itm); + } + } + else if(atom->type == forge->Sequence) + { + LV2_Atom_Sequence *seq = (LV2_Atom_Sequence *)atom; + + LV2_ATOM_SEQUENCE_FOREACH(seq, ev) + { + _sandbox_io_clean(forge, &ev->body); + } + } +} + +static inline bool +_sandbox_io_send(sandbox_io_t *io, uint32_t index, + uint32_t size, uint32_t protocol, const void *buf) +{ + LV2_Atom_Forge_Frame frame; + + if(protocol == 0) + protocol = io->float_protocol; + + lv2_atom_forge_object(&io->forge, &frame, 0, protocol); + + lv2_atom_forge_key(&io->forge, io->core_index); + lv2_atom_forge_int(&io->forge, index); + + if(protocol == io->float_protocol) + { + const float *value = buf; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + lv2_atom_forge_float(&io->forge, *value); + } + else if(protocol == io->peak_protocol) + { + const LV2UI_Peak_Data *peak_data = buf; + + lv2_atom_forge_key(&io->forge, io->ui_period_start); + lv2_atom_forge_int(&io->forge, peak_data->period_start); + + lv2_atom_forge_key(&io->forge, io->ui_period_size); + lv2_atom_forge_int(&io->forge, peak_data->period_size); + + lv2_atom_forge_key(&io->forge, io->ui_peak); + lv2_atom_forge_float(&io->forge, peak_data->peak); + } + else if(protocol == io->event_transfer) + { + const LV2_Atom *atom = buf; + LV2_Atom_Forge_Ref ref; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + ref = lv2_atom_forge_atom(&io->forge, atom->size, atom->type); + lv2_atom_forge_write(&io->forge, LV2_ATOM_BODY_CONST(atom), atom->size); + + LV2_Atom *src= lv2_atom_forge_deref(&io->forge, ref); + _sandbox_io_clean(&io->forge, src); + } + else if(protocol == io->atom_transfer) + { + const LV2_Atom *atom = buf; + LV2_Atom_Forge_Ref ref; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + ref = lv2_atom_forge_atom(&io->forge, atom->size, atom->type); + lv2_atom_forge_write(&io->forge, LV2_ATOM_BODY_CONST(atom), atom->size); + + LV2_Atom *src= lv2_atom_forge_deref(&io->forge, ref); + _sandbox_io_clean(&io->forge, src); + } + else if(protocol == io->ui_port_subscribe) + { + const sandbox_io_subscription_t *sub = buf; + + lv2_atom_forge_key(&io->forge, io->rdf_value); + lv2_atom_forge_bool(&io->forge, sub->state); + + lv2_atom_forge_key(&io->forge, io->ui_protocol); + lv2_atom_forge_urid(&io->forge, protocol); + } + + lv2_atom_forge_pop(&io->forge, &frame); + + //lv2_atom_forge_pop(&io->forge, &io->frame); + + return _sandbox_io_flush(io); +} + +static inline int +_sandbox_io_fd_get(sandbox_io_t *io) +{ + int fd = -1; + size_t sz = sizeof(int); + + if(io->sock == -1) + return -1; + + if(nn_getsockopt(io->sock, NN_SOL_SOCKET, NN_RCVFD, &fd, &sz) < 0) + return -1; + + //printf("_sandbox_io_fd_get: %i\n", fd); + + return fd; +} + +static inline int +_sandbox_io_init(sandbox_io_t *io, LV2_URID_Map *map, LV2_URID_Unmap *unmap, + const char *socket_path, bool is_master) +{ + io->base_uri = "file:///tmp/base"; + io->subject = serd_node_from_string(SERD_URI, (const uint8_t *)("")); + io->predicate = serd_node_from_string(SERD_URI, (const uint8_t *)(LV2_ATOM__atomTransfer)); + + io->map = map; + io->unmap = unmap; + + io->ser.offset = 0; + io->ser.size = 1024; + io->ser.buf = malloc(io->ser.size); + if(!io->ser.buf) + return -1; + + if(!(io->sratom = sratom_new(map))) + return -1; + sratom_set_pretty_numbers(io->sratom, false); + sratom_set_object_mode(io->sratom, SRATOM_OBJECT_MODE_BLANK); + + if((io->sock = nn_socket(AF_SP, NN_PAIR)) == -1) + return -1; + + const int ms = 10000; + if(nn_setsockopt(io->sock, NN_SOL_SOCKET, NN_LINGER, &ms, sizeof(ms)) == -1) + return -1; + + if(is_master) + { + if((io->id = nn_bind(io->sock, socket_path)) == -1) + return -1; + } + else // is_slave + { + if((io->id = nn_connect(io->sock, socket_path)) == -1) + return -1; + } + + lv2_atom_forge_init(&io->forge, map); + + io->float_protocol = map->map(map->handle, LV2_UI_PREFIX"floatProtocol"); + io->peak_protocol = map->map(map->handle, LV2_UI_PREFIX"peakProtocol"); + io->event_transfer = map->map(map->handle, LV2_ATOM__eventTransfer); + io->atom_transfer = map->map(map->handle, LV2_ATOM__atomTransfer); + io->core_index = map->map(map->handle, LV2_CORE__index); + io->rdf_value = map->map(map->handle, RDF_PREFIX"value"); + io->ui_protocol = map->map(map->handle, LV2_UI_PREFIX"protocol"); + io->ui_period_start = map->map(map->handle, LV2_UI_PREFIX"periodStart"); + io->ui_period_size = map->map(map->handle, LV2_UI_PREFIX"periodSize"); + io->ui_peak = map->map(map->handle, LV2_UI_PREFIX"peak"); + io->ui_window_title = map->map(map->handle, LV2_UI__windowTitle); + io->ui_port_subscribe = map->map(map->handle, LV2_UI__portSubscribe); + + _sandbox_io_reset(io); + + return 0; +} + +static inline void +_sandbox_io_deinit(sandbox_io_t *io) +{ + if(io->id != -1) + { + int res = nn_shutdown(io->sock, io->id); + if(res < 0) + fprintf(stderr, "nn_shutdown failed: %s\n", nn_strerror(nn_errno())); + } + + if(io->sock != -1) + { + int res = nn_close(io->sock); + if(res < 0) + fprintf(stderr, "nn_close failed: %s\n", nn_strerror(nn_errno())); + } + + if(io->sratom) + sratom_free(io->sratom); + + if(io->ser.buf) + free(io->ser.buf); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sandbox_ui.lv2/sandbox_master.c b/sandbox_ui.lv2/sandbox_master.c new file mode 100644 index 0000000..c61e1d0 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_master.c @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sandbox_master.h> +#include <sandbox_io.h> + +#include <lv2/lv2plug.in/ns/ext/log/log.h> +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +struct _sandbox_master_t { + sandbox_io_t io; + + sandbox_master_driver_t *driver; + void *data; +}; + +sandbox_master_t * +sandbox_master_new(sandbox_master_driver_t *driver, void *data) +{ + sandbox_master_t *sb = calloc(1, sizeof(sandbox_master_t)); + if(!sb) + goto fail; + + sb->driver = driver; + sb->data = data; + + if(_sandbox_io_init(&sb->io, driver->map, driver->unmap, driver->socket_path, true)) + goto fail; + + return sb; + +fail: + sandbox_master_free(sb); + return NULL; +} + +void +sandbox_master_free(sandbox_master_t *sb) +{ + if(sb) + { + _sandbox_io_deinit(&sb->io); + free(sb); + } +} + +void +sandbox_master_recv(sandbox_master_t *sb) +{ + if(sb) + _sandbox_io_recv(&sb->io, sb->driver->recv_cb, sb->driver->subscribe_cb, sb->data); +} + +bool +sandbox_master_send(sandbox_master_t *sb, uint32_t index, uint32_t size, + uint32_t format, const void *buf) +{ + if(sb) + return _sandbox_io_send(&sb->io, index, size, format, buf); + + return false; +} + +bool +sandbox_master_flush(sandbox_master_t *sb) +{ + if(sb) + return _sandbox_io_flush(&sb->io); + + return false; +} + +void +sandbox_master_fd_get(sandbox_master_t *sb, int *fd) +{ + if(sb && fd) + *fd = _sandbox_io_fd_get(&sb->io); + else if(fd) + *fd = -1; +} diff --git a/sandbox_ui.lv2/sandbox_master.h b/sandbox_ui.lv2/sandbox_master.h new file mode 100644 index 0000000..287a319 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_master.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_MASTER_H +#define _SANDBOX_MASTER_H + +#include <lv2/lv2plug.in/ns/ext/urid/urid.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _sandbox_master_t sandbox_master_t; +typedef struct _sandbox_master_driver_t sandbox_master_driver_t; +typedef void (*sandbox_master_recv_cb_t)(void *data, uint32_t index, uint32_t size, + uint32_t format, const void *buf); +typedef void (*sandbox_master_subscribe_cb_t)(void *data, uint32_t index, + uint32_t protocol, bool state); + +struct _sandbox_master_driver_t { + const char *socket_path; + LV2_URID_Map *map; + LV2_URID_Unmap *unmap; + sandbox_master_recv_cb_t recv_cb; + sandbox_master_subscribe_cb_t subscribe_cb; +}; + +sandbox_master_t * +sandbox_master_new(sandbox_master_driver_t *driver, void *data); + +void +sandbox_master_free(sandbox_master_t *sb); + +void +sandbox_master_recv(sandbox_master_t *sb); + +bool +sandbox_master_send(sandbox_master_t *sb, uint32_t index, uint32_t size, + uint32_t format, const void *buf); + +bool +sandbox_master_flush(sandbox_master_t *sb); + +void +sandbox_master_fd_get(sandbox_master_t *sb, int *fd); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sandbox_ui.lv2/sandbox_slave.c b/sandbox_ui.lv2/sandbox_slave.c new file mode 100644 index 0000000..9e1e665 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_slave.c @@ -0,0 +1,563 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <dlfcn.h> +#include <string.h> +#include <ctype.h> + +#include <sandbox_slave.h> +#include <sandbox_io.h> + +#include <lv2/lv2plug.in/ns/ext/log/log.h> +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#include <lilv/lilv.h> +#include <symap.h> + +struct _sandbox_slave_t { + Symap *symap; + LV2_URID_Map map; + LV2_URID_Unmap unmap; + + LV2_Log_Log log; + + LV2UI_Port_Map port_map; + LV2UI_Port_Subscribe port_subscribe; + + LV2UI_Resize host_resize; + const LV2UI_Resize *client_resize; + + const LV2UI_Idle_Interface *idle_iface; + + LilvWorld *world; + LilvNode *bundle_node; + LilvNode *plugin_node; + LilvNode *ui_node; + + const LilvPlugin *plug; + const LilvUI *ui; + + void *lib; + const LV2UI_Descriptor *desc; + void *handle; + + sandbox_io_t io; + + const sandbox_slave_driver_t *driver; + void *data; + + const char *plugin_uri; + const char *bundle_path; + const char *ui_uri; + const char *socket_path; + const char *window_title; +}; + +static inline LV2_URID +_map(void *data, const char *uri) +{ + sandbox_slave_t *sb= data; + + return symap_map(sb->symap, uri); +} + +static inline const char * +_unmap(void *data, LV2_URID urid) +{ + sandbox_slave_t *sb= data; + + return symap_unmap(sb->symap, urid); +} + +static inline int +_log_vprintf(LV2_Log_Handle handle, LV2_URID type, const char *fmt, va_list args) +{ + sandbox_slave_t *sb = handle; + + vprintf(fmt, args); + + return 0; +} + +static inline int +_log_printf(LV2_Log_Handle handle, LV2_URID type, const char *fmt, ...) +{ + va_list args; + + va_start (args, fmt); + const int ret = _log_vprintf(handle, type, fmt, args); + va_end(args); + + return ret; +} + +static inline uint32_t +_port_index(LV2UI_Feature_Handle handle, const char *symbol) +{ + sandbox_slave_t *sb = handle; + uint32_t index = LV2UI_INVALID_PORT_INDEX; + + LilvNode *symbol_uri = lilv_new_string(sb->world, symbol); + if(symbol_uri) + { + const LilvPort *port = lilv_plugin_get_port_by_symbol(sb->plug, symbol_uri); + + if(port) + index = lilv_port_get_index(sb->plug, port); + + lilv_node_free(symbol_uri); + } + + return index; +} + +static inline void +_write_function(LV2UI_Controller controller, uint32_t index, + uint32_t size, uint32_t protocol, const void *buf) +{ + sandbox_slave_t *sb = controller; + + const bool more = _sandbox_io_send(&sb->io, index, size, protocol, buf); + (void)more; //TODO +} + +static inline uint32_t +_port_subscribe(LV2UI_Feature_Handle handle, uint32_t index, uint32_t protocol, + const LV2_Feature *const *features) +{ + sandbox_slave_t *sb = handle; + + const sandbox_io_subscription_t sub = { + .state = 1, + .protocol = protocol + }; + _write_function(handle, index, sizeof(sandbox_io_subscription_t), sb->io.ui_port_subscribe, &sub); + + return 0; +} + +static inline uint32_t +_port_unsubscribe(LV2UI_Feature_Handle handle, uint32_t index, uint32_t protocol, + const LV2_Feature *const *features) +{ + sandbox_slave_t *sb = handle; + + const sandbox_io_subscription_t sub = { + .state = 0, + .protocol = protocol + }; + _write_function(handle, index, sizeof(sandbox_io_subscription_t), sb->io.ui_port_subscribe, &sub); + + return 0; +} + +static inline void +_sandbox_recv_cb(LV2UI_Handle handle, uint32_t index, uint32_t size, + uint32_t protocol, const void *buf) +{ + sandbox_slave_t *sb = handle; + + if(sb->desc && sb->desc->port_event) + sb->desc->port_event(sb->handle, index, size, protocol, buf); +} + +static inline int +_sandbox_resize(sandbox_slave_t *sb, int w, int h) +{ + if(sb->client_resize) + return sb->client_resize->ui_resize(sb->data, w, h); + + return 0; +} + +sandbox_slave_t * +sandbox_slave_new(int argc, char **argv, const sandbox_slave_driver_t *driver, void *data) +{ + sandbox_slave_t *sb = calloc(1, sizeof(sandbox_slave_t)); + if(!sb) + { + fprintf(stderr, "allocation failed\n"); + goto fail; + } + + int c; + while((c = getopt(argc, argv, "p:b:u:s:w:")) != -1) + { + switch(c) + { + case 'p': + sb->plugin_uri = optarg; + break; + case 'b': + sb->bundle_path = optarg; + break; + case 'u': + sb->ui_uri = optarg; + break; + case 's': + sb->socket_path = optarg; + break; + case 'w': + sb->window_title = optarg; + break; + case '?': + if( (optopt == 'p') || (optopt == 'b') || (optopt == 'u') || (optopt == 's') || (optopt == 'w') ) + fprintf(stderr, "Option `-%c' requires an argument.\n", optopt); + else if(isprint(optopt)) + fprintf(stderr, "Unknown option `-%c'.\n", optopt); + else + fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); + goto fail; + default: + goto fail; + } + } + + if( !sb->plugin_uri + || !sb->bundle_path + || !sb->ui_uri + || !sb->socket_path + || !sb->window_title) + { + fprintf(stderr, "invalid arguments\n"); + goto fail; + } + + sb->driver = driver; + sb->data = data; + + sb->map.handle = sb; + sb->map.map = _map; + + sb->unmap.handle = sb; + sb->unmap.unmap = _unmap; + + sb->log.handle = sb; + sb->log.printf = _log_printf; + sb->log.vprintf = _log_vprintf; + + sb->port_map.handle = sb; + sb->port_map.port_index = _port_index; + + sb->port_subscribe.handle = sb; + sb->port_subscribe.subscribe = _port_subscribe; + sb->port_subscribe.unsubscribe = _port_unsubscribe; + + sb->host_resize.handle = data; + sb->host_resize.ui_resize = driver->resize_cb; + + if(!(sb->symap = symap_new())) + { + fprintf(stderr, "symap_new failed\n"); + goto fail; + } + + if(!(sb->world = lilv_world_new())) + { + fprintf(stderr, "lilv_world_new failed\n"); + goto fail; + } + + sb->bundle_node = lilv_new_file_uri(sb->world, NULL, sb->bundle_path); + sb->plugin_node = lilv_new_uri(sb->world, sb->plugin_uri); + sb->ui_node = lilv_new_uri(sb->world, sb->ui_uri); + + if(!sb->bundle_node || !sb->plugin_node || !sb->ui_node) + { + fprintf(stderr, "lilv_new_uri failes\n"); + goto fail; + } + + lilv_world_load_bundle(sb->world, sb->bundle_node); + lilv_world_load_resource(sb->world, sb->ui_node); + + const LilvPlugins *plugins = lilv_world_get_all_plugins(sb->world); + if(!plugins) + { + fprintf(stderr, "lilv_world_get_all_plugins failed\n"); + goto fail; + } + + if(!(sb->plug = lilv_plugins_get_by_uri(plugins, sb->plugin_node))) + { + fprintf(stderr, "lilv_plugins_get_by_uri failed\n"); + goto fail; + } + + LilvUIs *uis = lilv_plugin_get_uis(sb->plug); + if(!uis) + { + fprintf(stderr, "lilv_plugin_get_uis failed\n"); + goto fail; + } + + if(!(sb->ui = lilv_uis_get_by_uri(uis, sb->ui_node))) + { + fprintf(stderr, "lilv_uis_get_by_uri failed\n"); + goto fail; + } + + const LilvNode *ui_path = lilv_ui_get_binary_uri(sb->ui); + if(!ui_path) + { + fprintf(stderr, "lilv_ui_get_binary_uri failed\n"); + goto fail; + } + +#if defined(LILV_0_22) + char *binary_path = lilv_file_uri_parse(lilv_node_as_string(ui_path), NULL); +#else + const char *binary_path = lilv_uri_to_path(lilv_node_as_string(ui_path)); +#endif + if(!(sb->lib = dlopen(binary_path, RTLD_LAZY))) + { + fprintf(stderr, "dlopen failed: %s\n", dlerror()); + goto fail; + } + +#if defined(LILV_0_22) + lilv_free(binary_path); +#endif + + LV2UI_DescriptorFunction desc_func = dlsym(sb->lib, "lv2ui_descriptor"); + if(!desc_func) + { + fprintf(stderr, "dlsym failed\n"); + goto fail; + } + + for(int i=0; true; i++) + { + const LV2UI_Descriptor *desc = desc_func(i); + if(!desc) // sentinel + break; + + if(!strcmp(desc->URI, sb->ui_uri)) + { + sb->desc = desc; + break; + } + } + + if(!sb->desc) + { + fprintf(stderr, "LV2UI_Descriptor lookup failed\n"); + goto fail; + } + + if(sb->desc->extension_data) + { + sb->idle_iface = sb->desc->extension_data(LV2_UI__idleInterface); + sb->client_resize= sb->desc->extension_data(LV2_UI__resize); + } + + if(_sandbox_io_init(&sb->io, &sb->map, &sb->unmap, sb->socket_path, false)) + { + fprintf(stderr, "_sandbox_io_init failed\n"); + goto fail; + } + + if(driver->init_cb && (driver->init_cb(sb, data) != 0) ) + { + fprintf(stderr, "driver->init_cb failed\n"); + goto fail; + } + + return sb; // success + +fail: + sandbox_slave_free(sb); + return NULL; +} + +void +sandbox_slave_free(sandbox_slave_t *sb) +{ + if(!sb) + return; + + if(sb->desc && sb->desc->cleanup) + sb->desc->cleanup(sb->handle); + + if(sb->driver && sb->driver->deinit_cb) + sb->driver->deinit_cb(sb->data); + + _sandbox_io_deinit(&sb->io); + + if(sb->lib) + dlclose(sb->lib); + + if(sb->world) + { + if(sb->ui_node) + { + lilv_world_unload_resource(sb->world, sb->ui_node); + lilv_node_free(sb->ui_node); + } + + if(sb->bundle_node) + { + lilv_world_unload_bundle(sb->world, sb->bundle_node); + lilv_node_free(sb->bundle_node); + } + + if(sb->plugin_node) + lilv_node_free(sb->plugin_node); + + lilv_world_free(sb->world); + } + + if(sb->symap) + symap_free(sb->symap); + + free(sb); +} + +int +sandbox_slave_instantiate(sandbox_slave_t *sb, void *parent, void *widget) +{ + LV2_Options_Option options [] = { + [0] = { + .context = LV2_OPTIONS_INSTANCE, + .subject = 0, + .key = sb->io.ui_window_title, + .size = strlen(sb->plugin_uri) + 1, + .type = sb->io.forge.String, + .value = sb->plugin_uri + }, + [1] = { + .key = 0, + .value = NULL + } + }; + + const LV2_Feature map_feature = { + .URI = LV2_URID__map, + .data = &sb->map + }; + const LV2_Feature unmap_feature = { + .URI = LV2_URID__unmap, + .data = &sb->unmap + }; + const LV2_Feature parent_feature = { + .URI = LV2_UI__parent, + .data = parent + }; + const LV2_Feature log_feature = { + .URI = LV2_LOG__log, + .data = &sb->log + }; + const LV2_Feature port_map_feature = { + .URI = LV2_UI__portMap, + .data = &sb->port_map + }; + const LV2_Feature port_subscribe_feature = { + .URI = LV2_UI__portSubscribe, + .data = &sb->port_subscribe + }; + const LV2_Feature idle_feature = { + .URI = LV2_UI__idleInterface, + .data = NULL + }; + const LV2_Feature resize_feature = { + .URI = LV2_UI__resize, + .data = &sb->host_resize + }; + const LV2_Feature options_feature = { + .URI = LV2_OPTIONS__options, + .data = options + }; + + const LV2_Feature *const features [] = { + &map_feature, + &unmap_feature, + &parent_feature, + &log_feature, + &port_map_feature, + &port_subscribe_feature, + &idle_feature, + &options_feature, + sb->host_resize.ui_resize ? &resize_feature : NULL, + NULL + }; + + if(sb->desc && sb->desc->instantiate) + { + sb->handle = sb->desc->instantiate(sb->desc, sb->plugin_uri, + sb->bundle_path, _write_function, sb, widget, features); + } + + if(sb->handle) + return 0; // success + + return -1; +} + +void +sandbox_slave_recv(sandbox_slave_t *sb) +{ + if(sb) + _sandbox_io_recv(&sb->io, _sandbox_recv_cb, NULL, sb); +} + +bool +sandbox_slave_flush(sandbox_slave_t *sb) +{ + if(sb) + return _sandbox_io_flush(&sb->io); + + return false; +} + +int +sandbox_slave_idle(sandbox_slave_t *sb) +{ + if(sb && sb->idle_iface) + return sb->idle_iface->idle(sb->handle); + + return 0; +} + +void +sandbox_slave_run(sandbox_slave_t *sb) +{ + if(sb && sb->driver && sb->driver->run_cb) + sb->driver->run_cb(sb, sb->data); +} + +void +sandbox_slave_fd_get(sandbox_slave_t *sb, int *fd) +{ + if(sb && fd) + *fd = _sandbox_io_fd_get(&sb->io); + else if(fd) + *fd = -1; +} + +const char * +sandbox_slave_title_get(sandbox_slave_t *sb) +{ + if(sb) + return sb->window_title; + + return NULL; +} diff --git a/sandbox_ui.lv2/sandbox_slave.h b/sandbox_ui.lv2/sandbox_slave.h new file mode 100644 index 0000000..321919a --- /dev/null +++ b/sandbox_ui.lv2/sandbox_slave.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_SLAVE_H +#define _SANDBOX_SLAVE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _sandbox_slave_t sandbox_slave_t; +typedef struct _sandbox_slave_driver_t sandbox_slave_driver_t; + +typedef int (*sandbox_slave_driver_init_t)(sandbox_slave_t *sb, void *data); +typedef void (*sandbox_slave_driver_run_t)(sandbox_slave_t *sb, void *data); +typedef void (*sandbox_slave_driver_deinit_t)(void *data); +typedef int (*sandbox_slave_driver_resize_t)(void *data, int width, int height); + +struct _sandbox_slave_driver_t { + sandbox_slave_driver_init_t init_cb; + sandbox_slave_driver_run_t run_cb; + sandbox_slave_driver_deinit_t deinit_cb; + sandbox_slave_driver_resize_t resize_cb; +}; + +sandbox_slave_t * +sandbox_slave_new(int argc, char **argv, const sandbox_slave_driver_t *driver, void *data); + +void +sandbox_slave_free(sandbox_slave_t *sb); + +int +sandbox_slave_instantiate(sandbox_slave_t *sb, void *parent, void *widget); + +void +sandbox_slave_recv(sandbox_slave_t *sb); + +bool +sandbox_slave_flush(sandbox_slave_t *sb); + +int +sandbox_slave_idle(sandbox_slave_t *sb); + +void +sandbox_slave_run(sandbox_slave_t *sb); + +void +sandbox_slave_fd_get(sandbox_slave_t *sb, int *fd); + +const char * +sandbox_slave_title_get(sandbox_slave_t *sb); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sandbox_ui.lv2/sandbox_ui.c b/sandbox_ui.lv2/sandbox_ui.c new file mode 100644 index 0000000..482ff41 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_ui.c @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <sys/wait.h> +#include <errno.h> +#include <signal.h> +#include <string.h> + +#include <lv2/lv2plug.in/ns/ext/options/options.h> +#include <lv2/lv2plug.in/ns/ext/atom/atom.h> + +#include <sandbox_ui.h> +#include <sandbox_master.h> +#include <lv2_external_ui.h> // kxstudio external-ui extension + +#define SOCKET_PATH_LEN 32 + +typedef struct _plughandle_t plughandle_t; + +struct _plughandle_t { + int done; + + LV2UI_Write_Function write_function; + LV2UI_Controller controller; + LV2UI_Port_Subscribe *subscribe; + + sandbox_master_driver_t driver; + sandbox_master_t *sb; + + char *plugin_uri; + char *bundle_path; + char *executable; + char *ui_uri; + char *window_title; + char socket_path [SOCKET_PATH_LEN]; + + LV2_URID ui_window_title; + LV2_URID atom_string; + + pid_t pid; + + struct { + LV2_External_UI_Widget widget; + const LV2_External_UI_Host *host; + } kx; +}; + +static void +_recv(LV2UI_Controller controller, uint32_t port, + uint32_t size, uint32_t protocol, const void *buf) +{ + plughandle_t *handle = controller; + + handle->write_function(handle->controller, port, size, protocol, buf); +} + +static void +_subscribe(LV2UI_Controller controller, uint32_t port, + uint32_t protocol, bool state) +{ + plughandle_t *handle = controller; + + if(handle->subscribe) + { + if(state) + handle->subscribe->subscribe(handle->subscribe->handle, + port, protocol, NULL); + else + handle->subscribe->unsubscribe(handle->subscribe->handle, + port, protocol, NULL); + } +} + +// Show Interface +static inline int +_show_cb(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + if(!handle->done) + return 0; // already showing + + strncpy(handle->socket_path, "ipc:///tmp/sandbox_ui_XXXXXX", SOCKET_PATH_LEN); + int fd = mkstemp(&handle->socket_path[6]); + if(!fd) + return -1; + close(fd); + + handle->sb = sandbox_master_new(&handle->driver, handle); + if(!handle->sb) + return -1; + + handle->pid = fork(); + if(handle->pid == 0) // child + { + char *const argv [] = { + handle->executable, + "-p", handle->plugin_uri, + "-b", handle->bundle_path, + "-u", handle->ui_uri, + "-s", handle->socket_path, + "-w", handle->window_title, + NULL + }; + execv(handle->executable, argv); // p = search PATH for executable + + printf("fork child failed\n"); + exit(-1); + } + else if(handle->pid < 0) + { + printf("fork failed\n"); + return -1; + } + + handle->done = 0; + + return 0; +} + +static inline int +_hide_cb(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + if(handle->pid > 0) // has child + { + kill(handle->pid, SIGINT); + + int status; + waitpid(handle->pid, &status, 0); + + handle->pid = -1; // invalidate + } + + if(handle->sb) + { + sandbox_master_free(handle->sb); + handle->sb = NULL; + } + + /* FIXME + remove(&handle->socket_path[6]); + */ + + if(handle->kx.host && handle->kx.host->ui_closed) + handle->kx.host->ui_closed(handle->controller); + + handle->done = 1; + + return 0; +} + +static const LV2UI_Show_Interface show_ext = { + .show = _show_cb, + .hide = _hide_cb +}; + +// Idle interface +static inline int +_idle_cb(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + if(handle->pid > 0) + { + int status; + int res; + if((res = waitpid(handle->pid, &status, WNOHANG)) < 0) + { + if(errno == ECHILD) + { + handle->pid = -1; // invalidate + //_hide_cb(ui); + handle->done = 1; + } + } + else if( (res > 0) && WIFEXITED(status) ) + { + handle->pid = -1; // invalidate + //_hide_cb(ui); + handle->done = 1; + } + } + + if(!handle->done && handle->sb) + { + sandbox_master_recv(handle->sb); + sandbox_master_flush(handle->sb); + } + + return handle->done; +} + +static const LV2UI_Idle_Interface idle_ext = { + .idle = _idle_cb +}; + +// External-UI Interface +static inline void +_kx_run(LV2_External_UI_Widget *widget) +{ + plughandle_t *handle = (void *)widget - offsetof(plughandle_t, kx.widget); + + if(_idle_cb(handle)) + _hide_cb(handle); +} + +static inline void +_kx_hide(LV2_External_UI_Widget *widget) +{ + plughandle_t *handle = (void *)widget - offsetof(plughandle_t, kx.widget); + + _hide_cb(handle); +} + +static inline void +_kx_show(LV2_External_UI_Widget *widget) +{ + plughandle_t *handle = (void *)widget - offsetof(plughandle_t, kx.widget); + + _show_cb(handle); +} + +static inline void +_free_strdups(plughandle_t *handle) +{ + if(handle->plugin_uri) + free(handle->plugin_uri); + if(handle->bundle_path) + free(handle->bundle_path); + if(handle->executable) + free(handle->executable); + if(handle->ui_uri) + free(handle->ui_uri); + if(handle->window_title) + free(handle->window_title); +}; + +LV2UI_Handle +sandbox_ui_instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri, + const char *bundle_path, LV2UI_Write_Function write_function, + LV2UI_Controller controller, LV2UI_Widget *widget, + const LV2_Feature *const *features) +{ + plughandle_t *handle = calloc(1, sizeof(plughandle_t)); + if(!handle) + return NULL; + + handle->write_function = write_function; + handle->controller = controller; + + LV2_Options_Option *opts = NULL; // optional + for(unsigned i=0; features[i]; i++) + { + if(!strcmp(features[i]->URI, LV2_URID__map)) + handle->driver.map = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_URID__unmap)) + handle->driver.unmap = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_UI__portSubscribe)) + handle->subscribe = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_EXTERNAL_UI__Host)) + handle->kx.host = features[i]->data; + else if(!strcmp(features[i]->URI, LV2_OPTIONS__options)) + opts = features[i]->data; + } + + if(!handle->driver.map || !handle->driver.unmap) + { + free(handle); + return NULL; + } + + handle->ui_window_title = handle->driver.map->map(handle->driver.map->handle, + LV2_UI__windowTitle); + handle->atom_string = handle->driver.map->map(handle->driver.map->handle, + LV2_ATOM__String); + + handle->plugin_uri = strdup(plugin_uri); + handle->bundle_path = strdup(bundle_path); + if(asprintf(&handle->executable, "%ssandbox_efl", bundle_path) == -1) + handle->executable = NULL; // failed + handle->ui_uri = strdup(descriptor->URI); + sprintf(&handle->ui_uri[strlen(handle->ui_uri) - 4], "%s", "3_eo"); //TODO more elegant way? + + if(opts) + { + for(LV2_Options_Option *opt = opts; + (opt->key != 0) && (opt->value != NULL); + opt++) + { + if( (opt->key == handle->ui_window_title) && (opt->type == handle->atom_string) ) + { + handle->window_title = strdup(opt->value); + break; + } + } + } + if(!handle->window_title && handle->kx.host && handle->kx.host->plugin_human_id) + handle->window_title = strdup(handle->kx.host->plugin_human_id); + if(!handle->window_title) + handle->window_title = strdup(descriptor->URI); + + if(!handle->plugin_uri || !handle->bundle_path || !handle->executable || !handle->ui_uri || !handle->window_title) + { + _free_strdups(handle); + free(handle); + return NULL; + } + + handle->driver.socket_path = handle->socket_path; + handle->driver.recv_cb = _recv; + handle->driver.subscribe_cb = _subscribe; + + handle->pid = -1; // invalidate + + handle->kx.widget.run = _kx_run; + handle->kx.widget.show = _kx_show; + handle->kx.widget.hide = _kx_hide; + + if(strstr(descriptor->URI, "_kx")) + *(LV2_External_UI_Widget **)widget = &handle->kx.widget; + else + *widget = NULL; + + handle->done = 1; + + return handle; +} + +void +sandbox_ui_cleanup(LV2UI_Handle instance) +{ + plughandle_t *handle = instance; + + _free_strdups(handle); + free(handle); +} + +void +sandbox_ui_port_event(LV2UI_Handle instance, uint32_t index, uint32_t size, + uint32_t protocol, const void *buf) +{ + plughandle_t *handle = instance; + + if(handle->sb) + sandbox_master_send(handle->sb, index, size, protocol, buf); +} + +// extension data callback for show interface UI +const void * +sandbox_ui_extension_data(const char *uri) +{ + if(!strcmp(uri, LV2_UI__idleInterface)) + return &idle_ext; + else if(!strcmp(uri, LV2_UI__showInterface)) + return &show_ext; + + return NULL; +} diff --git a/sandbox_ui.lv2/sandbox_ui.h b/sandbox_ui.lv2/sandbox_ui.h new file mode 100644 index 0000000..6dee0e3 --- /dev/null +++ b/sandbox_ui.lv2/sandbox_ui.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch) + * + * This is free software: you can redistribute it and/or modify + * it under the terms of the Artistic License 2.0 as published by + * The Perl Foundation. + * + * This source is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Artistic License 2.0 for more details. + * + * You should have received a copy of the Artistic License 2.0 + * along the source as a COPYING file. If not, obtain it from + * http://www.perlfoundation.org/artistic_license_2_0. + */ + +#ifndef _SANDBOX_UI_H +#define _SANDBOX_UI_H + +#include <lv2/lv2plug.in/ns/extensions/ui/ui.h> + +#ifdef __cplusplus +extern "C" { +#endif + +LV2UI_Handle +sandbox_ui_instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri, + const char *bundle_path, LV2UI_Write_Function write_function, + LV2UI_Controller controller, LV2UI_Widget *widget, + const LV2_Feature *const *features); + +void +sandbox_ui_cleanup(LV2UI_Handle instance); + +void +sandbox_ui_port_event(LV2UI_Handle instance, uint32_t index, uint32_t size, + uint32_t protocol, const void *buf); + +const void * +sandbox_ui_extension_data(const char *uri); + +#ifdef __cplusplus +} +#endif + +#endif |