diff --git a/src/jack.c b/src/jack.c index 82fbb42..4d8a46f 100644 --- a/src/jack.c +++ b/src/jack.c @@ -27,6 +27,7 @@ #include "jalv_internal.h" #include "worker.h" +#include "zix/atomic.h" struct JalvBackend { jack_client_t* client; ///< Jack client @@ -208,8 +209,12 @@ jack_process_cb(jack_nframes_t nframes, void* data) if (port->flow == FLOW_OUTPUT && port->type == TYPE_CONTROL && lilv_port_has_property(jalv->plugin, port->lilv_port, jalv->nodes.lv2_reportsLatency)) { - if (jalv->plugin_latency != port->control) { - jalv->plugin_latency = port->control; + /* The shared memory will only be manipulated by this thread. + * Therefore reading is safe. + */ + const float latency = port->control.data[0]; + if (jalv->plugin_latency != latency) { + jalv->plugin_latency = latency; jack_recompute_total_latencies(client); } } else if (port->flow == FLOW_OUTPUT && port->type == TYPE_EVENT) { @@ -244,7 +249,11 @@ jack_process_cb(jack_nframes_t nframes, void* data) ev->index = p; ev->protocol = 0; ev->size = sizeof(float); - *(float*)ev->body = port->control; + /* The shared memory will only be manipulated by this thread. + * Therefore reading is safe. + */ + float* const dest = (float*)ev->body; + *dest = port->control.data[0]; if (zix_ring_write(jalv->plugin_events, buf, sizeof(buf)) < sizeof(buf)) { fprintf(stderr, "Plugin => UI buffer overflow!\n"); @@ -461,7 +470,7 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) /* Connect the port based on its type */ switch (port->type) { case TYPE_CONTROL: - lilv_instance_connect_port(jalv->instance, port_index, &port->control); + lilv_instance_connect_port(jalv->instance, port_index, port->control.data); break; case TYPE_AUDIO: port->sys_port = jack_port_register( @@ -511,6 +520,16 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) #endif } +int +jalv_backend_instance_name(Jalv* jalv, char* const name, const size_t size) +{ + const char* const client_name = jack_get_client_name(jalv->backend->client); + strncpy(name, client_name, size); + name[size-1] = 0; + + return 0; +} + /** * @brief jalv_backend_is_same_cycle * @param jalv diff --git a/src/jalv.c b/src/jalv.c index df7c779..1dc1f7a 100644 --- a/src/jalv.c +++ b/src/jalv.c @@ -39,6 +39,8 @@ #include "jalv_config.h" #include "jalv_internal.h" +#include "ipc_controls.h" +#include "zix/atomic.h" #include "lv2/lv2plug.in/ns/ext/atom/atom.h" #include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h" @@ -150,8 +152,7 @@ die(const char* msg) */ static void create_port(Jalv* jalv, - uint32_t port_index, - float default_value) + uint32_t port_index) { struct Port* const port = &jalv->ports[port_index]; @@ -160,7 +161,9 @@ create_port(Jalv* jalv, port->evbuf = NULL; port->buf_size = 0; port->index = port_index; - port->control = 0.0f; + port->control.shm_index = -1; + port->control.data = NULL; + port->control.new_data = NAN; port->flow = FLOW_UNKNOWN; const bool optional = lilv_port_has_property( @@ -184,7 +187,6 @@ create_port(Jalv* jalv, /* Set control values */ if (lilv_port_is_a(jalv->plugin, port->lilv_port, jalv->nodes.lv2_ControlPort)) { port->type = TYPE_CONTROL; - port->control = isnan(default_value) ? 0.0f : default_value; if (!hidden) { add_control(&jalv->controls, new_port_control(jalv, port->index)); } @@ -221,12 +223,9 @@ jalv_create_ports(Jalv* jalv) { jalv->num_ports = lilv_plugin_get_num_ports(jalv->plugin); jalv->ports = (struct Port*)calloc(jalv->num_ports, sizeof(struct Port)); - float* default_values = (float*)calloc( - lilv_plugin_get_num_ports(jalv->plugin), sizeof(float)); - lilv_plugin_get_port_ranges_float(jalv->plugin, NULL, NULL, default_values); for (uint32_t i = 0; i < jalv->num_ports; ++i) { - create_port(jalv, i, default_values[i]); + create_port(jalv, i); } const LilvPort* control_input = lilv_plugin_get_port_by_designation( @@ -234,8 +233,6 @@ jalv_create_ports(Jalv* jalv) if (control_input) { jalv->control_in = lilv_port_get_index(jalv->plugin, control_input); } - - free(default_values); } /** @@ -365,7 +362,11 @@ jalv_set_control(const ControlID* control, Jalv* jalv = control->jalv; if (control->type == PORT && type == jalv->forge.Float) { struct Port* port = &control->jalv->ports[control->index]; - port->control = *(const float*)body; + /* This value will be written from different threads e.g. gtk and JACK processing CB + * and atomic copy is not guaranteed on all platforms (e.g. unaligned access on ARM). + * Therefore we cannot directly write to port->control + */ + ZIX_ATOMIC_WRITE(&port->control.new_data, *(const float*)body); } else if (control->type == PROPERTY) { // Copy forge since it is used by process thread LV2_Atom_Forge forge = jalv->forge; @@ -525,7 +526,8 @@ jalv_apply_ui_events(Jalv* jalv, uint32_t nframes) struct Port* const port = &jalv->ports[ev.index]; if (ev.protocol == 0) { assert(ev.size == sizeof(float)); - port->control = *(float*)body; + /* only be called inside the lock. Therefore no atomic access required */ + port->control.data[0] = *(float*)body; } else if (ev.protocol == jalv->urids.atom_eventTransfer) { LV2_Evbuf_Iterator e = lv2_evbuf_end(port->evbuf); const LV2_Atom* const atom = (const LV2_Atom*)body; @@ -608,6 +610,11 @@ jalv_send_to_ui(Jalv* jalv, bool jalv_run(Jalv* jalv, uint32_t nframes) { + /* lock control data shared memory for write access */ + if (jalv_api_ctl_lock(jalv, true) != 0) { + die("api_ctl_lock() failed"); + } + /* Read and apply control change events from UI */ jalv_apply_ui_events(jalv, nframes); @@ -623,6 +630,11 @@ jalv_run(Jalv* jalv, uint32_t nframes) jalv->worker.iface->end_run(jalv->instance->lv2_handle); } + /* unlock control data shared memory for write access. + * Up to now only read access is allowed. + */ + jalv_api_ctl_unlock(jalv, true); + /* Check if it's time to send updates to the UI */ jalv->event_delta_t += nframes; bool send_ui_updates = false; @@ -1008,6 +1020,12 @@ jalv_open(Jalv* const jalv, int argc, char** argv) return -6; } + if (jalv_api_ctl_init(jalv) != 0) { + fprintf(stderr, "Init of external IPC API failed\n"); + jalv_close(jalv); + return -7; + } + printf("Sample rate: %u Hz\n", jalv->sample_rate); printf("Block length: %u frames\n", jalv->block_length); printf("MIDI buffers: %zu bytes\n", jalv->midi_buf_size); @@ -1151,7 +1169,11 @@ jalv_open(Jalv* const jalv, int argc, char** argv) ControlID* control = jalv->controls.controls[i]; if (control->type == PORT && control->is_writable) { struct Port* port = &jalv->ports[control->index]; - jalv_print_control(jalv, port, port->control); + /* Backend thread is not yet started. + * Therefore there is no other thread + * which writes to this variable + */ + jalv_print_control(jalv, port, port->control.data[0]); } } @@ -1199,6 +1221,8 @@ jalv_close(Jalv* const jalv) lilv_instance_free(jalv->instance); } + jalv_api_ctl_destroy(jalv); + /* Clean up */ free(jalv->ports); zix_ring_free(jalv->ui_events); diff --git a/src/jalv_console.c b/src/jalv_console.c index 07e97c6..9f91b62 100644 --- a/src/jalv_console.c +++ b/src/jalv_console.c @@ -26,6 +26,7 @@ #include "jalv_config.h" #include "jalv_internal.h" +#include "zix/atomic.h" #include "lv2/lv2plug.in/ns/extensions/ui/ui.h" @@ -46,6 +47,7 @@ print_usage(const char* name, bool error) fprintf(os, " -t Print trace messages from plugin\n"); fprintf(os, " -u UUID UUID for Jack session restoration\n"); fprintf(os, " -x Exact JACK client name (exit if taken)\n"); + fprintf(os, " -g GROUP User group name or ID used for creating JALV API IPC resources\n"); return error ? 1 : 0; } @@ -120,6 +122,14 @@ jalv_init(int* argc, char*** argv, JalvOptions* opts) opts->name = jalv_strdup((*argv)[a]); } else if ((*argv)[a][1] == 'x') { opts->name_exact = 1; + } else if ((*argv)[a][1] == 'g') { + if (++a == *argc) { + fprintf(stderr, "Missing argument for -g\n"); + return 1; + } + free(opts->user_group); + opts->user_group = jalv_strdup((*argv)[a]); + } else { fprintf(stderr, "Unknown option %s\n", (*argv)[a]); return print_usage((*argv)[0], true); @@ -145,7 +155,7 @@ jalv_print_controls(Jalv* jalv, bool writable, bool readable) struct Port* const port = &jalv->ports[control->index]; printf("%s = %f\n", lilv_node_as_string(control->symbol), - port->control); + port->control.data[0]); } } } @@ -191,7 +201,11 @@ jalv_process_command(Jalv* jalv, const char* cmd) jalv_print_controls(jalv, false, true); } else if (sscanf(cmd, "set %u %f", &index, &value) == 2) { if (index < jalv->num_ports) { - jalv->ports[index].control = value; + /* This method will be called by main thread + * after starting the backend thread. + * Therefore we cannot directly write to port->control + */ + ZIX_ATOMIC_WRITE(&jalv->ports[index].control.new_data, value); jalv_print_control(jalv, &jalv->ports[index], value); } else { fprintf(stderr, "error: port index out of range\n"); @@ -208,7 +222,11 @@ jalv_process_command(Jalv* jalv, const char* cmd) } } if (port) { - port->control = value; + /* This method will be called by main thread + * after starting the backend thread. + * Therefore we cannot directly write to port->control + */ + ZIX_ATOMIC_WRITE(&port->control.new_data, value); jalv_print_control(jalv, port, value); } else { fprintf(stderr, "error: no control named `%s'\n", sym); diff --git a/src/jalv_internal.h b/src/jalv_internal.h index e9ae907..fa1ca6f 100644 --- a/src/jalv_internal.h +++ b/src/jalv_internal.h @@ -44,6 +44,7 @@ #include "zix/ring.h" #include "zix/sem.h" #include "zix/thread.h" +#include "jalv.h" #include "sratom/sratom.h" @@ -87,7 +88,32 @@ struct Port { void* widget; ///< Control widget, if applicable size_t buf_size; ///< Custom buffer size, or 0 uint32_t index; ///< Port index - float control; ///< For control ports, otherwise 0.0f + struct { + int32_t shm_index; ///< Control port index in the shared memory + /** + * @brief data must only be accessed by the backend thread or + * when the backend thread was not yet started. + * The memory of this pointer will also be accessed by the loaded Lv2 plug-in. + * A Lv2 plug-in does not guarantee that it accesses this memory in an atomic manner. + * Therefore it could also result in invalid data + * when this memory will be accessed from a different thread + * with atomic read or writes. + * Instead of that + * jalv_api_ctl_read_port() should be used to read this memory and + * the first element of the memory can be changed by + * writing atomically to new_data. + */ + float* data; ///< For control ports, otherwise NULL + /** + * @brief new_data + * *data will be overwritten by this value + * before executing the run() function of the Lv2 plug-in. + * This variable will be accessed by different threads. + * Therefore atomic access has to be used. + */ + atomic_float new_data; ///< For control ports, otherwise NaN + } control; + bool old_api; ///< True for event, false for atom }; /* Controls */ @@ -175,6 +201,7 @@ typedef struct { int show_ui; ///< Show non-embedded UI int print_controls; ///< Print control changes to stdout int non_interactive; ///< Do not listen for commands on stdin + char* user_group; ///< The user group which is used for the JALV API } JalvOptions; typedef struct { @@ -321,6 +348,19 @@ struct Jalv { void* window; ///< Window (if applicable) struct Port* ports; ///< Port array of size num_ports Controls controls; ///< Available plugin controls + jalv_api_t jalv_api; + struct { + size_t ctl_input_data_size; + char* ctl_input_data; ///< changed values when in blocking mode + ZixShm shm; + } jalv_api_blocking; + size_t ctl_data_size; + /** + * @brief prev_ctl_data contians the control data before the last changes + * This data will be compared with the data in jalv_api_t::shm + * to find the changed control port values + */ + char* prev_ctl_data; uint32_t block_length; ///< Audio buffer size (block length) size_t midi_buf_size; ///< Size of MIDI port buffers uint32_t control_in; ///< Index of control input port @@ -366,6 +406,9 @@ jalv_backend_close(Jalv* jalv); void jalv_backend_activate_port(Jalv* jalv, uint32_t port_index); +int +jalv_backend_instance_name(Jalv* jalv, char* const name, const size_t size); + uint32_t jalv_backend_get_process_cycle_id(const Jalv* const jalv); diff --git a/src/jalv_qt.cpp b/src/jalv_qt.cpp index 582361f..ece6bb6 100644 --- a/src/jalv_qt.cpp +++ b/src/jalv_qt.cpp @@ -19,6 +19,7 @@ #include #include "jalv_internal.h" +#include "zix/atomic.h" #include "lv2/lv2plug.in/ns/ext/patch/patch.h" #include "lv2/lv2plug.in/ns/ext/port-props/port-props.h" @@ -459,7 +460,21 @@ Control::Control(PortContainer portContainer, QWidget* parent) } // Find and set min, max and default values for port - float defaultValue = ndef ? lilv_node_as_float(ndef) : port->control; + float defaultValue = 0.0f; + if (ndef) { + defaultValue = lilv_node_as_float(ndef); + } else { + /* the audio backend thread is already running + * when calling this function. + * Therefore the read could be interrupted + * by a write of the backend thread. + * Which could result in invalid data + * if the read or the write was not atomic + * (e.g. on ARM when accessing unaligned) + * As a result port->control cannot be used for reading + */ + jalv_api_ctl_read_port_by_index(&portContainer.jalv->jalv_api, port->control.shm_index, sizeof(defaultValue), &defaultValue); + } setRange(lilv_node_as_float(nmin), lilv_node_as_float(nmax)); setValue(defaultValue); @@ -577,7 +592,11 @@ Control::dialChanged(int dialValue) float value = getValue(); label->setText(getValueLabel(value)); - port->control = value; + /* This method will be called by main thread + * after starting the backend thread. + * Therefore we cannot directly write to port->control + */ + ZIX_ATOMIC_WRITE(&port->control.new_data, value); } static bool diff --git a/src/portaudio.c b/src/portaudio.c index 8fc98d5..480d10d 100644 --- a/src/portaudio.c +++ b/src/portaudio.c @@ -20,6 +20,7 @@ #include "jalv_internal.h" #include "worker.h" +#include "zix/atomic.h" struct JalvBackend { PaStream* stream; @@ -96,7 +97,10 @@ pa_process_cb(const void* inputs, ev->index = p; ev->protocol = 0; ev->size = sizeof(float); - *(float*)ev->body = port->control; + /* The shared memory will only be manipulated by this thread. + * Therefore reading is safe. + */ + *(float*)ev->body = port->control.data[0]; if (zix_ring_write(jalv->plugin_events, buf, sizeof(buf)) < sizeof(buf)) { fprintf(stderr, "Plugin => UI buffer overflow!\n"); @@ -218,13 +222,22 @@ jalv_backend_activate_port(Jalv* jalv, uint32_t port_index) struct Port* const port = &jalv->ports[port_index]; switch (port->type) { case TYPE_CONTROL: - lilv_instance_connect_port(jalv->instance, port_index, &port->control); + lilv_instance_connect_port(jalv->instance, port_index, port->control.data); break; default: break; } } +int +jalv_backend_instance_name(Jalv* jalv, char* const name, const size_t size) +{ + const int pid = zix_pid(); + snprintf(name, size, "portaudio%u", pid); + + return 0; +} + uint32_t jalv_backend_get_process_cycle_id(const Jalv* const jalv) { diff --git a/src/state.c b/src/state.c index 899e61f..20435cc 100644 --- a/src/state.c +++ b/src/state.c @@ -30,6 +30,7 @@ #include "jalv_config.h" #include "jalv_internal.h" +#include "zix/atomic.h" #define NS_JALV "http://drobilla.net/ns/jalv#" #define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#" @@ -57,7 +58,11 @@ get_port_value(const char* port_symbol, if (port && port->flow == FLOW_INPUT && port->type == TYPE_CONTROL) { *size = sizeof(float); *type = jalv->forge.Float; - return &port->control; + // TODO will be called by gtk when lv2 is possibly running and changing the values + // Therefore invalid data could be read + // Possibly repeat safe process until lv2 processing was not executed in between + // -> use same meachnism as external IPC clients do + return port->control.data; } *size = *type = 0; return NULL; @@ -155,8 +160,11 @@ set_port_value(const char* port_symbol, } if (jalv->play_state != JALV_RUNNING) { - // Set value on port struct directly - port->control = fvalue; + /* This method will be called by main thread + * after starting the backend thread. + * Therefore we cannot directly write to port->control + */ + ZIX_ATOMIC_WRITE(&port->control.new_data, fvalue); } else { // Send value to running plugin jalv_ui_write(jalv, port->index, sizeof(fvalue), 0, &fvalue); diff --git a/src/zix/common.h b/src/zix/common.h index 5cf24c0..4c70397 100644 --- a/src/zix/common.h +++ b/src/zix/common.h @@ -17,6 +17,20 @@ #ifndef ZIX_COMMON_H #define ZIX_COMMON_H +#ifdef _WIN32 +# include +#else +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + /** @addtogroup zix @{ @@ -46,6 +60,13 @@ #endif /** @endcond */ +#ifdef _WIN32 +// TODO +#else +// TODO check if valid for OSX +#define ZIX_SHM_PATH "/dev/shm/" +#endif + #ifdef __cplusplus extern "C" { #else @@ -58,6 +79,28 @@ extern "C" { #define ZIX_UNUSED #endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#define STRCAT(DEST, STR1, STR2) \ + const size_t DEST##str1_size = strlen(STR1); \ + const size_t DEST##_size = strlen(STR1) + strlen(STR2) + 1; \ + char DEST[DEST##_size]; \ + strncpy(DEST, (STR1), DEST##_size); \ + DEST[DEST##_size - 1] = 0; \ + strncat(DEST, (STR2), DEST##_size - DEST##str1_size - 1); + + +typedef enum { + ZIX_MODE_RDONLY = (1<<0), + ZIX_MODE_WRONLY = (1<<1), + ZIX_MODE_RDWR = (ZIX_MODE_RDONLY | ZIX_MODE_WRONLY), + ZIX_MODE_CREATE = (1<<2), + ZIX_MODE_NONBLOCKING = (1<<3), +} ZixMode; + typedef enum { ZIX_STATUS_SUCCESS, ZIX_STATUS_ERROR, @@ -65,7 +108,9 @@ typedef enum { ZIX_STATUS_NOT_FOUND, ZIX_STATUS_EXISTS, ZIX_STATUS_BAD_ARG, - ZIX_STATUS_BAD_PERMS + ZIX_STATUS_BAD_PERMS, + ZIX_STATUS_EMPTY, + ZIX_STATUS_UNAVAILABLE, } ZixStatus; static inline const char* @@ -86,10 +131,34 @@ zix_strerror(const ZixStatus status) return "Bad argument"; case ZIX_STATUS_BAD_PERMS: return "Bad permissions"; + case ZIX_STATUS_EMPTY: + return "empty"; + case ZIX_STATUS_UNAVAILABLE: + return "Unavailable"; } return "Unknown error"; } + +typedef unsigned long long int ZixTime; +static inline ZixTime zix_time_get_us(void); + +#define INFO(FORMAT, ...) \ +{ \ + const ZixTime us = zix_time_get_us(); \ + const ZixTime us2sec = (ZixTime)1e6; \ + fprintf(stderr, "[%lld.%06lld %s:%u] " FORMAT "\n", us/us2sec, us%us2sec, __func__, __LINE__, __VA_ARGS__); \ +} + +#define ERR(FORMAT, ...) INFO("ERR: " FORMAT, __VA_ARGS__) + +#ifdef DEBUG +#define DBG INFO +#else +#define DBG(...) +#endif + + /** Function for comparing two elements. */ @@ -105,6 +174,17 @@ typedef bool (*ZixEqualFunc)(const void* a, const void* b); */ typedef void (*ZixDestroyFunc)(void* ptr); + + +static inline int zix_pid(void) +{ +#ifdef WIN32 + return _getpid(); +#else + return getpid(); +#endif +} + static inline size_t zix_round_up(const size_t value, const size_t boundary) { const size_t elem_count = (value + boundary - 1) / boundary; @@ -112,6 +192,80 @@ static inline size_t zix_round_up(const size_t value, const size_t boundary) } +static inline ZixStatus +zix_group_set(const int fd, const char* const group) +{ + if (!group || strlen(group) == 0) { + return ZIX_STATUS_EMPTY; + } + +#ifdef _WIN32 + INFO("User groups are not supported for this operating system. (fd %d, group %s)", fd, group); +#else + gid_t gid = -1; + + if (isdigit(*group)) { + const long gid_long = atol(group); + if (gid_long < 0 || gid_long > UINT_MAX) { + ERR("Group '%s' was converted to group ID %ld. This is an invalid ID.", group, gid_long); + return ZIX_STATUS_ERROR; + } + gid = gid_long; + } else { + /* as mentioned in the manpage of getgrnam() + * errno has to be set to 0 when reading it + */ + errno = 0; + const struct group* const g = getgrnam(group); + if (!g) { + perror("getgrnam() failed"); + return ZIX_STATUS_ERROR; + } + gid = g->gr_gid; + } + + if (fchown(fd, (uid_t)-1, gid) < 0) { + perror("fchown() failed"); + return ZIX_STATUS_ERROR; + } +#endif + + return ZIX_STATUS_SUCCESS; +} + + +static inline ZixTime +zix_time_get_us() +{ +#ifdef WIN32 + // TODO not yet implemented + return 0; +#else + struct timespec time; + + if (clock_gettime(CLOCK_MONOTONIC, &time) != 0) { + perror("clock_gettime() failed"); + return 0; + } + + return (ZixTime)time.tv_sec * 1e6 + + (ZixTime)time.tv_nsec / 1e3; +#endif +} + +static inline void +zix_sleep_us(const unsigned int us) +{ +#ifdef WIN32 + // TODO not yet implemented + ERR("Not yet implemented (%u us)", us); +#else + /* needs gnu11 */ + usleep(us); +#endif +} + + /** @} */ diff --git a/wscript b/wscript index 758678f..af5ce6f 100644 --- a/wscript +++ b/wscript @@ -41,7 +41,7 @@ def configure(conf): conf.load('compiler_c', cache=True) conf.load('compiler_cxx', cache=True) conf.load('autowaf', cache=True) - autowaf.set_c_lang(conf, 'c99') + autowaf.set_c_lang(conf, 'gnu11') autowaf.check_pkg(conf, 'lv2', atleast_version='1.14.0', uselib_store='LV2') autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV', @@ -161,9 +161,14 @@ def build(bld): src/state.c src/symap.c src/worker.c + src/ipc_controls.c src/zix/ring.c ''' + # C/C++ Headers + bld.install_files('${INCLUDEDIR}/jalv', 'src/jalv.h') + bld.install_files('${INCLUDEDIR}/zix', bld.path.ant_glob('src/zix/*.h')) + if bld.env.HAVE_JACK: source += 'src/jack.c' @@ -172,7 +177,7 @@ def build(bld): source = source + ' src/jalv_console.c', target = 'jalv', includes = ['.', 'src'], - lib = ['pthread'], + lib = ['pthread', 'rt'], install_path = '${LIBDIR}/jack') autowaf.use_lib(bld, obj, libs) obj.env.cshlib_PATTERN = '%s.so' @@ -184,7 +189,7 @@ def build(bld): source = source + ' src/jalv_console.c', target = 'jalv', includes = ['.', 'src'], - lib = ['pthread'], + lib = ['pthread', 'rt'], install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs) @@ -194,7 +199,7 @@ def build(bld): source = source + ' src/jalv_gtk.c', target = 'jalv.gtk', includes = ['.', 'src'], - lib = ['pthread', 'm'], + lib = ['pthread', 'rt', 'm'], install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs + ' GTK2') @@ -204,7 +209,7 @@ def build(bld): source = source + ' src/jalv_gtk.c', target = 'jalv.gtk3', includes = ['.', 'src'], - lib = ['pthread', 'm'], + lib = ['pthread', 'rt', 'm'], install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs + ' GTK3') @@ -214,7 +219,7 @@ def build(bld): source = source + ' src/jalv_gtkmm2.cpp', target = 'jalv.gtkmm', includes = ['.', 'src'], - lib = ['pthread'], + lib = ['pthread', 'rt'], install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs + ' GTKMM2') @@ -227,7 +232,7 @@ def build(bld): source = source + ' src/jalv_qt.cpp', target = 'jalv.qt4', includes = ['.', 'src'], - lib = ['pthread'], + lib = ['pthread', 'rt'], install_path = '${BINDIR}') autowaf.use_lib(bld, obj, libs + ' QT4') @@ -240,7 +245,7 @@ def build(bld): source = source + ' src/jalv_qt.cpp', target = 'jalv.qt5', includes = ['.', 'src'], - lib = ['pthread'], + lib = ['pthread', 'rt'], install_path = '${BINDIR}', cxxflags = ['-fPIC']) autowaf.use_lib(bld, obj, libs + ' QT5')