aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2017-02-23 19:01:03 +0100
committerDavid Robillard <d@drobilla.net>2017-02-23 19:01:03 +0100
commitc505e04d5b21a317f99c0a8459b090428e845d2e (patch)
treeabd178d9f36e6cecd418a231908a3d040a80f247
parent0b587a635b966bee3569fbd5569b091827e33ceb (diff)
downloadjalv-osx.tar.gz
jalv-osx.tar.bz2
jalv-osx.zip
Preliminary native OSX workosx
-rw-r--r--src/jalv.c428
-rw-r--r--src/jalv_internal.h3
-rw-r--r--src/jalv_osx.m159
-rw-r--r--src/osx/Info.plist34
-rw-r--r--src/osx/Jalv.icnsbin0 -> 202450 bytes
-rw-r--r--src/osx/MainMenu.xib116
-rw-r--r--src/osx/MainWindow.xib31
-rw-r--r--src/osx/SelectPlugin.xib99
-rwxr-xr-xsrc/osx/bundleify.sh79
-rw-r--r--wscript28
10 files changed, 773 insertions, 204 deletions
diff --git a/src/jalv.c b/src/jalv.c
index 78901ca..6bf8c6b 100644
--- a/src/jalv.c
+++ b/src/jalv.c
@@ -688,6 +688,10 @@ jalv_run(Jalv* jalv, uint32_t nframes)
bool
jalv_update(Jalv* jalv)
{
+ if (!jalv->plugin) {
+ return true;
+ }
+
/* Check quit flag and close if set. */
if (zix_sem_try_wait(&exit_sem)) {
jalv_close_ui(jalv);
@@ -759,6 +763,217 @@ jalv_apply_control_arg(Jalv* jalv, const char* s)
return true;
}
+int
+jalv_load_plugin(Jalv* jalv, const LilvNode* plugin_uri, LilvState* state)
+{
+ LilvWorld* world = jalv->world;
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(world);
+
+ /* Find plugin */
+ printf("Plugin: %s\n", lilv_node_as_string(plugin_uri));
+ jalv->plugin = lilv_plugins_get_by_uri(plugins, plugin_uri);
+ /* lilv_node_free(plugin_uri); */
+ if (!jalv->plugin) {
+ fprintf(stderr, "Failed to find plugin\n");
+ lilv_world_free(world);
+ return EXIT_FAILURE;
+ }
+
+ /* Load preset, if specified */
+ if (jalv->opts.preset) {
+ LilvNode* preset = lilv_new_uri(jalv->world, jalv->opts.preset);
+
+ jalv_load_presets(jalv, NULL, NULL);
+ state = lilv_state_new_from_world(jalv->world, &jalv->map, preset);
+ jalv->preset = state;
+ lilv_node_free(preset);
+ if (!state) {
+ fprintf(stderr, "Failed to find preset <%s>\n", jalv->opts.preset);
+ lilv_world_free(world);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Check that any required features are supported */
+ LilvNodes* req_feats = lilv_plugin_get_required_features(jalv->plugin);
+ LILV_FOREACH(nodes, f, req_feats) {
+ const char* uri = lilv_node_as_uri(lilv_nodes_get(req_feats, f));
+ if (!feature_is_supported(uri)) {
+ fprintf(stderr, "Feature %s is not supported\n", uri);
+ lilv_world_free(world);
+ return EXIT_FAILURE;
+ }
+ }
+ lilv_nodes_free(req_feats);
+
+ /* Check for thread-safe state restore() method. */
+ LilvNode* state_threadSafeRestore = lilv_new_uri(
+ jalv->world, LV2_STATE__threadSafeRestore);
+ if (lilv_plugin_has_feature(jalv->plugin, state_threadSafeRestore)) {
+ jalv->safe_restore = true;
+ }
+ lilv_node_free(state_threadSafeRestore);
+
+ if (!state) {
+ /* Not restoring state, load the plugin as a preset to get default */
+ state = lilv_state_new_from_world(
+ jalv->world, &jalv->map, lilv_plugin_get_uri(jalv->plugin));
+ }
+
+ /* Get a plugin UI */
+ const char* native_ui_type_uri = jalv_native_ui_type(jalv);
+ jalv->uis = lilv_plugin_get_uis(jalv->plugin);
+ if (!jalv->opts.generic_ui && native_ui_type_uri) {
+ const LilvNode* native_ui_type = lilv_new_uri(jalv->world, native_ui_type_uri);
+ LILV_FOREACH(uis, u, jalv->uis) {
+ const LilvUI* this_ui = lilv_uis_get(jalv->uis, u);
+ if (lilv_ui_is_supported(this_ui,
+ suil_ui_supported,
+ native_ui_type,
+ &jalv->ui_type)) {
+ /* TODO: Multiple UI support */
+ jalv->ui = this_ui;
+ break;
+ }
+ }
+ } else if (!jalv->opts.generic_ui && jalv->opts.show_ui) {
+ jalv->ui = lilv_uis_get(jalv->uis, lilv_uis_begin(jalv->uis));
+ }
+
+ /* Create ringbuffers for UI if necessary */
+ if (jalv->ui) {
+ fprintf(stderr, "UI: %s\n",
+ lilv_node_as_uri(lilv_ui_get_uri(jalv->ui)));
+ } else {
+ fprintf(stderr, "UI: None\n");
+ }
+
+ /* Create port and control structures */
+ jalv_create_ports(jalv);
+ jalv_create_controls(jalv, true);
+ jalv_create_controls(jalv, false);
+
+ if (!(jalv->backend = jalv_backend_init(jalv))) {
+ die("Failed to connect to audio system");
+ }
+
+ 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);
+
+ if (jalv->opts.buffer_size == 0) {
+ /* The UI ring is fed by plugin output ports (usually one), and the UI
+ updates roughly once per cycle. The ring size is a few times the
+ size of the MIDI output to give the UI a chance to keep up. The UI
+ should be able to keep up with 4 cycles, and tests show this works
+ for me, but this value might need increasing to avoid overflows.
+ */
+ jalv->opts.buffer_size = jalv->midi_buf_size * N_BUFFER_CYCLES;
+ }
+
+ if (jalv->opts.update_rate == 0.0) {
+ /* Calculate a reasonable UI update frequency. */
+ jalv->ui_update_hz = (float)jalv->sample_rate / jalv->midi_buf_size * 2.0f;
+ jalv->ui_update_hz = MAX(25.0f, jalv->ui_update_hz);
+ } else {
+ /* Use user-specified UI update rate. */
+ jalv->ui_update_hz = jalv->opts.update_rate;
+ jalv->ui_update_hz = MAX(1.0f, jalv->ui_update_hz);
+ }
+
+ /* The UI can only go so fast, clamp to reasonable limits */
+ jalv->ui_update_hz = MIN(60, jalv->ui_update_hz);
+ jalv->opts.buffer_size = MAX(4096, jalv->opts.buffer_size);
+ fprintf(stderr, "Comm buffers: %d bytes\n", jalv->opts.buffer_size);
+ fprintf(stderr, "Update rate: %.01f Hz\n", jalv->ui_update_hz);
+
+ /* Build options array to pass to plugin */
+ const LV2_Options_Option options[] = {
+ { LV2_OPTIONS_INSTANCE, 0, jalv->urids.param_sampleRate,
+ sizeof(float), jalv->urids.atom_Float, &jalv->sample_rate },
+ { LV2_OPTIONS_INSTANCE, 0, jalv->urids.bufsz_minBlockLength,
+ sizeof(int32_t), jalv->urids.atom_Int, &jalv->block_length },
+ { LV2_OPTIONS_INSTANCE, 0, jalv->urids.bufsz_maxBlockLength,
+ sizeof(int32_t), jalv->urids.atom_Int, &jalv->block_length },
+ { LV2_OPTIONS_INSTANCE, 0, jalv->urids.bufsz_sequenceSize,
+ sizeof(int32_t), jalv->urids.atom_Int, &jalv->midi_buf_size },
+ { LV2_OPTIONS_INSTANCE, 0, jalv->urids.ui_updateRate,
+ sizeof(float), jalv->urids.atom_Float, &jalv->ui_update_hz },
+ { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
+ };
+
+ options_feature.data = (void*)&options;
+
+ /* Create Plugin <=> UI communication buffers */
+ jalv->ui_events = zix_ring_new(jalv->opts.buffer_size);
+ jalv->plugin_events = zix_ring_new(jalv->opts.buffer_size);
+ zix_ring_mlock(jalv->ui_events);
+ zix_ring_mlock(jalv->plugin_events);
+
+ /* Instantiate the plugin */
+ jalv->instance = lilv_plugin_instantiate(
+ jalv->plugin, jalv->sample_rate, features);
+ if (!jalv->instance) {
+ die("Failed to instantiate plugin.\n");
+ }
+
+ ext_data.data_access = lilv_instance_get_descriptor(jalv->instance)->extension_data;
+
+ fprintf(stderr, "\n");
+ if (!jalv->buf_size_set) {
+ jalv_allocate_port_buffers(jalv);
+ }
+
+ /* Create workers if necessary */
+ if (lilv_plugin_has_feature(jalv->plugin, jalv->nodes.work_schedule)
+ && lilv_plugin_has_extension_data(jalv->plugin, jalv->nodes.work_interface)) {
+ const LV2_Worker_Interface* iface = (const LV2_Worker_Interface*)
+ lilv_instance_get_extension_data(jalv->instance, LV2_WORKER__interface);
+
+ jalv_worker_init(jalv, &jalv->worker, iface, true);
+ if (jalv->safe_restore) {
+ jalv_worker_init(jalv, &jalv->state_worker, iface, false);
+ }
+ }
+
+ /* Apply loaded state to plugin instance if necessary */
+ if (state) {
+ jalv_apply_state(jalv, state);
+ }
+
+ if (jalv->opts.controls) {
+ for (char** c = jalv->opts.controls; *c; ++c) {
+ jalv_apply_control_arg(jalv, *c);
+ }
+ }
+
+ /* Set Jack callbacks */
+ jalv_backend_init(jalv);
+
+ /* Create Jack ports and connect plugin ports to buffers */
+ for (uint32_t i = 0; i < jalv->num_ports; ++i) {
+ jalv_backend_activate_port(jalv, i);
+ }
+
+ /* Print initial control values */
+ for (size_t i = 0; i < jalv->controls.n_controls; ++i) {
+ ControlID* control = jalv->controls.controls[i];
+ if (control->type == PORT) {// && control->value_type == jalv->forge.Float) {
+ struct Port* port = &jalv->ports[control->index];
+ print_control_value(jalv, port, port->control);
+ }
+ }
+
+ /* Activate plugin */
+ lilv_instance_activate(jalv->instance);
+
+ /* Activate audio backend */
+ jalv_backend_activate(jalv);
+ jalv->play_state = JALV_RUNNING;
+
+ return 0;
+}
+
static void
signal_handler(int ignored)
{
@@ -884,7 +1099,6 @@ main(int argc, char** argv)
LilvWorld* world = lilv_world_new();
lilv_world_load_all(world);
jalv.world = world;
- const LilvPlugins* plugins = lilv_world_get_all_plugins(world);
/* Cache URIs for concepts we'll use */
jalv.nodes.atom_AtomPort = lilv_new_uri(world, LV2_ATOM__AtomPort);
@@ -948,216 +1162,22 @@ main(int argc, char** argv)
plugin_uri = lilv_new_uri(world, argv[argc - 1]);
}
- if (!plugin_uri) {
- fprintf(stderr, "Missing plugin URI, try lv2ls to list plugins\n");
- return EXIT_FAILURE;
- }
-
- /* Find plugin */
- printf("Plugin: %s\n", lilv_node_as_string(plugin_uri));
- jalv.plugin = lilv_plugins_get_by_uri(plugins, plugin_uri);
- lilv_node_free(plugin_uri);
- if (!jalv.plugin) {
- fprintf(stderr, "Failed to find plugin\n");
- lilv_world_free(world);
- return EXIT_FAILURE;
- }
-
- /* Load preset, if specified */
- if (jalv.opts.preset) {
- LilvNode* preset = lilv_new_uri(jalv.world, jalv.opts.preset);
-
- jalv_load_presets(&jalv, NULL, NULL);
- state = lilv_state_new_from_world(jalv.world, &jalv.map, preset);
- jalv.preset = state;
- lilv_node_free(preset);
- if (!state) {
- fprintf(stderr, "Failed to find preset <%s>\n", jalv.opts.preset);
- lilv_world_free(world);
- return EXIT_FAILURE;
- }
- }
-
- /* Check that any required features are supported */
- LilvNodes* req_feats = lilv_plugin_get_required_features(jalv.plugin);
- LILV_FOREACH(nodes, f, req_feats) {
- const char* uri = lilv_node_as_uri(lilv_nodes_get(req_feats, f));
- if (!feature_is_supported(uri)) {
- fprintf(stderr, "Feature %s is not supported\n", uri);
- lilv_world_free(world);
- return EXIT_FAILURE;
- }
- }
- lilv_nodes_free(req_feats);
-
- /* Check for thread-safe state restore() method. */
- LilvNode* state_threadSafeRestore = lilv_new_uri(
- jalv.world, LV2_STATE__threadSafeRestore);
- if (lilv_plugin_has_feature(jalv.plugin, state_threadSafeRestore)) {
- jalv.safe_restore = true;
- }
- lilv_node_free(state_threadSafeRestore);
-
- if (!state) {
- /* Not restoring state, load the plugin as a preset to get default */
- state = lilv_state_new_from_world(
- jalv.world, &jalv.map, lilv_plugin_get_uri(jalv.plugin));
- }
+ /* if (!plugin_uri) { */
+ /* fprintf(stderr, "Missing plugin URI, try lv2ls to list plugins\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
- /* Get a plugin UI */
- const char* native_ui_type_uri = jalv_native_ui_type(&jalv);
- jalv.uis = lilv_plugin_get_uis(jalv.plugin);
- if (!jalv.opts.generic_ui && native_ui_type_uri) {
- const LilvNode* native_ui_type = lilv_new_uri(jalv.world, native_ui_type_uri);
- LILV_FOREACH(uis, u, jalv.uis) {
- const LilvUI* this_ui = lilv_uis_get(jalv.uis, u);
- if (lilv_ui_is_supported(this_ui,
- suil_ui_supported,
- native_ui_type,
- &jalv.ui_type)) {
- /* TODO: Multiple UI support */
- jalv.ui = this_ui;
- break;
- }
+ if (plugin_uri) {
+ /* Load plugin */
+ const int st = jalv_load_plugin(&jalv, plugin_uri, state);
+ if (st) {
+ return st;
}
- } else if (!jalv.opts.generic_ui && jalv.opts.show_ui) {
- jalv.ui = lilv_uis_get(jalv.uis, lilv_uis_begin(jalv.uis));
}
- /* Create ringbuffers for UI if necessary */
- if (jalv.ui) {
- fprintf(stderr, "UI: %s\n",
- lilv_node_as_uri(lilv_ui_get_uri(jalv.ui)));
- } else {
- fprintf(stderr, "UI: None\n");
- }
-
- /* Create port and control structures */
- jalv_create_ports(&jalv);
- jalv_create_controls(&jalv, true);
- jalv_create_controls(&jalv, false);
-
- if (!(jalv.backend = jalv_backend_init(&jalv))) {
- die("Failed to connect to audio system");
- }
-
- 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);
-
- if (jalv.opts.buffer_size == 0) {
- /* The UI ring is fed by plugin output ports (usually one), and the UI
- updates roughly once per cycle. The ring size is a few times the
- size of the MIDI output to give the UI a chance to keep up. The UI
- should be able to keep up with 4 cycles, and tests show this works
- for me, but this value might need increasing to avoid overflows.
- */
- jalv.opts.buffer_size = jalv.midi_buf_size * N_BUFFER_CYCLES;
- }
-
- if (jalv.opts.update_rate == 0.0) {
- /* Calculate a reasonable UI update frequency. */
- jalv.ui_update_hz = (float)jalv.sample_rate / jalv.midi_buf_size * 2.0f;
- jalv.ui_update_hz = MAX(25.0f, jalv.ui_update_hz);
- } else {
- /* Use user-specified UI update rate. */
- jalv.ui_update_hz = jalv.opts.update_rate;
- jalv.ui_update_hz = MAX(1.0f, jalv.ui_update_hz);
- }
-
- /* The UI can only go so fast, clamp to reasonable limits */
- jalv.ui_update_hz = MIN(60, jalv.ui_update_hz);
- jalv.opts.buffer_size = MAX(4096, jalv.opts.buffer_size);
- fprintf(stderr, "Comm buffers: %d bytes\n", jalv.opts.buffer_size);
- fprintf(stderr, "Update rate: %.01f Hz\n", jalv.ui_update_hz);
-
- /* Build options array to pass to plugin */
- const LV2_Options_Option options[] = {
- { LV2_OPTIONS_INSTANCE, 0, jalv.urids.param_sampleRate,
- sizeof(float), jalv.urids.atom_Float, &jalv.sample_rate },
- { LV2_OPTIONS_INSTANCE, 0, jalv.urids.bufsz_minBlockLength,
- sizeof(int32_t), jalv.urids.atom_Int, &jalv.block_length },
- { LV2_OPTIONS_INSTANCE, 0, jalv.urids.bufsz_maxBlockLength,
- sizeof(int32_t), jalv.urids.atom_Int, &jalv.block_length },
- { LV2_OPTIONS_INSTANCE, 0, jalv.urids.bufsz_sequenceSize,
- sizeof(int32_t), jalv.urids.atom_Int, &jalv.midi_buf_size },
- { LV2_OPTIONS_INSTANCE, 0, jalv.urids.ui_updateRate,
- sizeof(float), jalv.urids.atom_Float, &jalv.ui_update_hz },
- { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }
- };
-
- options_feature.data = (void*)&options;
-
- /* Create Plugin <=> UI communication buffers */
- jalv.ui_events = zix_ring_new(jalv.opts.buffer_size);
- jalv.plugin_events = zix_ring_new(jalv.opts.buffer_size);
- zix_ring_mlock(jalv.ui_events);
- zix_ring_mlock(jalv.plugin_events);
-
- /* Instantiate the plugin */
- jalv.instance = lilv_plugin_instantiate(
- jalv.plugin, jalv.sample_rate, features);
- if (!jalv.instance) {
- die("Failed to instantiate plugin.\n");
- }
-
- ext_data.data_access = lilv_instance_get_descriptor(jalv.instance)->extension_data;
-
- fprintf(stderr, "\n");
- if (!jalv.buf_size_set) {
- jalv_allocate_port_buffers(&jalv);
- }
-
- /* Create workers if necessary */
- if (lilv_plugin_has_feature(jalv.plugin, jalv.nodes.work_schedule)
- && lilv_plugin_has_extension_data(jalv.plugin, jalv.nodes.work_interface)) {
- const LV2_Worker_Interface* iface = (const LV2_Worker_Interface*)
- lilv_instance_get_extension_data(jalv.instance, LV2_WORKER__interface);
-
- jalv_worker_init(&jalv, &jalv.worker, iface, true);
- if (jalv.safe_restore) {
- jalv_worker_init(&jalv, &jalv.state_worker, iface, false);
- }
- }
-
- /* Apply loaded state to plugin instance if necessary */
- if (state) {
- jalv_apply_state(&jalv, state);
- }
-
- if (jalv.opts.controls) {
- for (char** c = jalv.opts.controls; *c; ++c) {
- jalv_apply_control_arg(&jalv, *c);
- }
- }
-
- /* Set Jack callbacks */
- jalv_backend_init(&jalv);
-
- /* Create Jack ports and connect plugin ports to buffers */
- for (uint32_t i = 0; i < jalv.num_ports; ++i) {
- jalv_backend_activate_port(&jalv, i);
- }
-
- /* Print initial control values */
- for (size_t i = 0; i < jalv.controls.n_controls; ++i) {
- ControlID* control = jalv.controls.controls[i];
- if (control->type == PORT) {// && control->value_type == jalv->forge.Float) {
- struct Port* port = &jalv.ports[control->index];
- print_control_value(&jalv, port, port->control);
- }
- }
-
- /* Activate plugin */
- lilv_instance_activate(jalv.instance);
-
/* Discover UI */
jalv.has_ui = jalv_discover_ui(&jalv);
- /* Activate Jack */
- jalv_backend_activate(&jalv);
- jalv.play_state = JALV_RUNNING;
-
/* Run UI (or prompt at console) */
jalv_open_ui(&jalv);
diff --git a/src/jalv_internal.h b/src/jalv_internal.h
index 3a565ec..711e256 100644
--- a/src/jalv_internal.h
+++ b/src/jalv_internal.h
@@ -363,6 +363,9 @@ jalv_set_control(const ControlID* control,
const char*
jalv_native_ui_type(Jalv* jalv);
+int
+jalv_load_plugin(Jalv* jalv, const LilvNode* plugin_uri, LilvState* state);
+
bool
jalv_discover_ui(Jalv* jalv);
diff --git a/src/jalv_osx.m b/src/jalv_osx.m
new file mode 100644
index 0000000..be970df
--- /dev/null
+++ b/src/jalv_osx.m
@@ -0,0 +1,159 @@
+#import <Cocoa/Cocoa.h>
+#import <Foundation/Foundation.h>
+#include "jalv_internal.h"
+
+@interface AppDelegate : NSObject <NSApplicationDelegate>
+@end
+
+@interface AppDelegate ()
+@property IBOutlet NSWindow* window;
+@property IBOutlet NSWindow* selectWindow;
+@property NSTimer* timer;
+@end
+
+static Jalv* g_jalv = NULL;
+NSWindowController* swc = NULL;
+NSWindowController* mwc = NULL;
+
+static const LilvPlugin*
+get_ith_plugin(unsigned i)
+{
+ const LilvPlugins* plugins = lilv_world_get_all_plugins(g_jalv->world);
+ LilvIter* p = lilv_plugins_begin(plugins);
+ for (unsigned j = 0; j < i; ++j) {
+ p = lilv_plugins_next(plugins, p);
+ }
+ return lilv_plugins_get(plugins, p);
+}
+
+@interface TableController : NSObject <NSTableViewDataSource, NSTableViewDelegate>
+@end
+
+@implementation TableController
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView*)tableView
+{
+ return lilv_plugins_size(lilv_world_get_all_plugins(g_jalv->world));
+}
+
+- (id)tableView:(NSTableView*)tableView
+objectValueForTableColumn:(NSTableColumn*)tableColumn
+ row:(NSInteger)row
+{
+ const LilvPlugin* plugin = get_ith_plugin(row);
+ if ([tableColumn.identifier isEqualToString:@"URI"]) {
+ return [NSString stringWithUTF8String:lilv_node_as_string(lilv_plugin_get_uri(plugin))];
+ } else if ([tableColumn.identifier isEqualToString:@"Name"]) {
+ return [NSString stringWithUTF8String:lilv_node_as_string(lilv_plugin_get_name(plugin))];
+ } else {
+ return @"?";
+ }
+}
+
+- (void)tableViewSelectionDidChange:(NSNotification*)notification
+{
+ // Get selected plugin
+ Jalv* jalv = g_jalv;
+ NSTableView* tableView = notification.object;
+ const LilvPlugin* plugin = get_ith_plugin(tableView.selectedRow);
+
+ // Load plugin and set up Jalv internals
+ jalv_load_plugin(g_jalv, lilv_plugin_get_uri(plugin), NULL);
+
+ // Load and show main window
+ mwc = [[NSWindowController alloc] initWithWindowNibName:@"MainWindow"];
+ [mwc showWindow:self];
+
+ if (jalv->ui && !jalv->opts.generic_ui) {
+ // Instantiate custom UI
+ jalv_ui_instantiate(jalv, jalv_native_ui_type(jalv), mwc.window.contentView);
+ }
+
+ if (g_jalv->ui_instance) {
+ NSView* ui = suil_instance_get_widget(g_jalv->ui_instance);
+ [mwc.window setContentSize:[ui frame].size];
+ } else {
+ NSRect frame = NSMakeRect(10, 10, 800, 100);
+ NSSlider* widget = [[NSSlider alloc] initWithFrame:frame];
+ [widget retain];
+ [mwc.window.contentView addSubview:widget];
+ }
+}
+
+@end
+
+@implementation AppDelegate
+
+- (void)openDocument:(NSNotification*)aNotification
+{
+}
+
+- (void)onTimer:(NSTimer*)timer
+{
+ jalv_update(g_jalv);
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
+{
+ Jalv* jalv = g_jalv;
+
+ self.timer = [
+ [NSTimer scheduledTimerWithTimeInterval: 1/(float)jalv->ui_update_hz
+ target: self
+ selector: @selector(onTimer:)
+ userInfo: NULL
+ repeats: YES] retain];
+
+ swc = [[NSWindowController alloc] initWithWindowNibName:@"SelectPlugin"];
+ [swc showWindow:self];
+}
+
+- (void)applicationWillTerminate:(NSNotification*)aNotification
+{
+}
+
+@end
+
+const char*
+jalv_native_ui_type(Jalv* jalv)
+{
+ return "http://lv2plug.in/ns/extensions/ui#CocoaUI";
+}
+
+int
+jalv_init(int* argc, char*** argv, JalvOptions* opts)
+{
+ return 0;
+}
+
+bool
+jalv_discover_ui(Jalv* jalv)
+{
+ return TRUE;
+}
+
+int
+jalv_open_ui(Jalv* jalv)
+{
+ g_jalv = jalv;
+ return NSApplicationMain(0, NULL);
+}
+
+void
+jalv_ui_port_event(Jalv* jalv,
+ uint32_t port_index,
+ uint32_t buffer_size,
+ uint32_t protocol,
+ const void* buffer)
+{
+ if (jalv->ui_instance) {
+ suil_instance_port_event(jalv->ui_instance, port_index,
+ buffer_size, protocol, buffer);
+ }
+}
+
+int
+jalv_close_ui(Jalv* jalv)
+{
+ return 0;
+}
diff --git a/src/osx/Info.plist b/src/osx/Info.plist
new file mode 100644
index 0000000..3192998
--- /dev/null
+++ b/src/osx/Info.plist
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleDisplayName</key>
+ <string>Jalv</string>
+ <key>CFBundleExecutable</key>
+ <string>Jalv</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Jalv, Copyright © 2016 David Robillard</string>
+ <key>CFBundleIconFile</key>
+ <string>Jalv</string>
+ <key>CFBundleIdentifier</key>
+ <string>net.drobilla.Jalv</string>
+ <key>CFBundleName</key>
+ <string>Jalv</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0.0</string>
+ <key>CFBundleSignature</key>
+ <string>djlv</string>
+ <key>LSApplicationCategoryType</key>
+ <string>public.app-category.music</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>Copyright © 2016 David Robillard.</string>
+ <key>NSMainNibFile</key>
+ <string>MainMenu</string>
+ <key>NSPrincipalClass</key>
+ <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/src/osx/Jalv.icns b/src/osx/Jalv.icns
new file mode 100644
index 0000000..f3e02ef
--- /dev/null
+++ b/src/osx/Jalv.icns
Binary files differ
diff --git a/src/osx/MainMenu.xib b/src/osx/MainMenu.xib
new file mode 100644
index 0000000..63148ba
--- /dev/null
+++ b/src/osx/MainMenu.xib
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <deployment identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+ <connections>
+ <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
+ <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+ <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+ <items>
+ <menuItem title="Jalv" id="1Xt-HY-uBw">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Jalv" systemMenu="apple" id="uQy-DD-JDr">
+ <items>
+ <menuItem title="About Jalv" id="5kV-Vb-QxS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+ <menuItem title="Hide Jalv" keyEquivalent="h" id="Olw-nP-bQN">
+ <connections>
+ <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+ <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+ <connections>
+ <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Show All" id="Kd2-mp-pUS">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+ </connections>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+ <menuItem title="Quit Jalv" keyEquivalent="q" id="4sb-4s-VLi">
+ <connections>
+ <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="File" id="dMs-cI-mzQ">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="File" id="bib-Uj-vzu">
+ <items>
+ <menuItem title="Open…" keyEquivalent="o" id="IAo-SY-fd9">
+ <connections>
+ <action selector="openDocument:" target="-1" id="bVn-NM-KNZ"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Open Recent" id="tXI-mr-wws">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Open Recent" systemMenu="recentDocuments" id="oas-Oc-fiZ">
+ <items>
+ <menuItem title="Clear Menu" id="vNY-rz-j42">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <connections>
+ <action selector="clearRecentDocuments:" target="-1" id="Daa-9d-B3U"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem isSeparatorItem="YES" id="m54-Is-iLE"/>
+ <menuItem title="Close" keyEquivalent="w" id="DVo-aG-piG">
+ <connections>
+ <action selector="performClose:" target="-1" id="HmO-Ls-i7Q"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save…" keyEquivalent="s" id="pxx-59-PXV">
+ <connections>
+ <action selector="saveDocument:" target="-1" id="teZ-XB-qJY"/>
+ </connections>
+ </menuItem>
+ <menuItem title="Save As…" keyEquivalent="S" id="Bw7-FT-i3A">
+ <connections>
+ <action selector="saveDocumentAs:" target="-1" id="mDf-zr-I0C"/>
+ </connections>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Presets" id="PQG-aa-8LK">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ <menu key="submenu" title="Presets" id="EjZ-NH-0oW">
+ <items>
+ <menuItem title="Save Preset..." id="TaK-sa-Ngz">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ <menuItem title="Delete Current Preset..." id="Wx1-UF-PfU">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </menuItem>
+ <menuItem title="Help" id="wpr-3q-Mcd">
+ <modifierMask key="keyEquivalentModifierMask"/>
+ </menuItem>
+ </items>
+ </menu>
+ </objects>
+</document>
diff --git a/src/osx/MainWindow.xib b/src/osx/MainWindow.xib
new file mode 100644
index 0000000..c07da00
--- /dev/null
+++ b/src/osx/MainWindow.xib
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G1108" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <deployment identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="NSWindowController">
+ <connections>
+ <outlet property="window" destination="VXJ-Fk-6kf" id="l2U-Nv-Glb"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <window title="Jalv" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" animationBehavior="default" id="VXJ-Fk-6kf">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+ <rect key="contentRect" x="335" y="390" width="480" height="360"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
+ <view key="contentView" id="OCp-hb-yvX">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </view>
+ <point key="canvasLocation" x="420" y="400"/>
+ </window>
+ <customObject id="G8J-6G-JMX" customClass="AppDelegate">
+ <connections>
+ <outlet property="window" destination="VXJ-Fk-6kf" id="j1L-IO-oxb"/>
+ </connections>
+ </customObject>
+ </objects>
+</document>
diff --git a/src/osx/SelectPlugin.xib b/src/osx/SelectPlugin.xib
new file mode 100644
index 0000000..a5cd0fd
--- /dev/null
+++ b/src/osx/SelectPlugin.xib
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15G1004" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <deployment identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner"/>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <window title="Select Plugin" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" animationBehavior="default" id="QvC-M9-y7g">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+ <rect key="contentRect" x="196" y="240" width="480" height="270"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
+ <view key="contentView" horizontalHuggingPriority="1" verticalHuggingPriority="1" id="EiT-Mj-1SZ">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <scrollView horizontalHuggingPriority="1" verticalHuggingPriority="1" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4pG-7L-hBF">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="271"/>
+ <clipView key="contentView" id="fdG-PK-0lT">
+ <rect key="frame" x="1" y="23" width="478" height="247"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" headerView="aqO-vT-hPD" id="TZA-3C-Hwj">
+ <rect key="frame" x="0.0" y="0.0" width="478" height="247"/>
+ <autoresizingMask key="autoresizingMask"/>
+ <size key="intercellSpacing" width="3" height="2"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ <tableViewGridLines key="gridStyleMask" vertical="YES"/>
+ <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
+ <tableColumns>
+ <tableColumn identifier="URI" width="116" minWidth="40" maxWidth="1000" id="eOU-Hb-AhL">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="URI">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="UOr-9T-74U">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ </tableColumn>
+ <tableColumn identifier="Name" width="356" minWidth="40" maxWidth="1000" id="nyV-6f-pju">
+ <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Name">
+ <font key="font" metaFont="smallSystem"/>
+ <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
+ </tableHeaderCell>
+ <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Qin-ze-9Kf">
+ <font key="font" metaFont="system"/>
+ <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </textFieldCell>
+ <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
+ </tableColumn>
+ </tableColumns>
+ <connections>
+ <outlet property="dataSource" destination="CGu-bu-G4s" id="mMt-kO-SCQ"/>
+ <outlet property="delegate" destination="CGu-bu-G4s" id="Ke5-ln-be4"/>
+ </connections>
+ </tableView>
+ </subviews>
+ <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
+ </clipView>
+ <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="CdQ-z1-cai">
+ <rect key="frame" x="1" y="7" width="0.0" height="16"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="35h-Jq-9bx">
+ <rect key="frame" x="-15" y="23" width="16" height="0.0"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </scroller>
+ <tableHeaderView key="headerView" id="aqO-vT-hPD">
+ <rect key="frame" x="0.0" y="0.0" width="478" height="23"/>
+ <autoresizingMask key="autoresizingMask"/>
+ </tableHeaderView>
+ </scrollView>
+ </subviews>
+ <constraints>
+ <constraint firstAttribute="trailing" secondItem="4pG-7L-hBF" secondAttribute="trailing" id="FY5-7s-SAO"/>
+ <constraint firstItem="4pG-7L-hBF" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="-1" id="YiE-ES-X8y"/>
+ <constraint firstItem="4pG-7L-hBF" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" id="ca2-7v-KVw"/>
+ <constraint firstItem="4pG-7L-hBF" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="-1" id="dpl-Gw-hgW"/>
+ <constraint firstAttribute="bottom" secondItem="4pG-7L-hBF" secondAttribute="bottom" id="lTW-Ah-X77"/>
+ </constraints>
+ </view>
+ <point key="canvasLocation" x="75" y="42"/>
+ </window>
+ <customObject id="CGu-bu-G4s" customClass="TableController"/>
+ <customObject id="kUh-F5-FTj" customClass="AppDelegate">
+ <connections>
+ <outlet property="selectWindow" destination="QvC-M9-y7g" id="Kwk-RP-6Dd"/>
+ </connections>
+ </customObject>
+ </objects>
+</document>
diff --git a/src/osx/bundleify.sh b/src/osx/bundleify.sh
new file mode 100755
index 0000000..3806bd2
--- /dev/null
+++ b/src/osx/bundleify.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+if [ "$#" != 3 ]; then
+ echo "USAGE: $0 LIB_PREFIX BUNDLE EXE";
+ exit 1;
+fi
+
+prefix=$1
+bundle=$2
+exe=$3
+
+mkdir -p "$bundle/Contents/lib"
+
+# Replace Control with Command in key bindings
+sed -i '' 's/GDK_CONTROL_MASK/GDK_META_MASK/' $bundle/Contents/patchage.ui
+
+# Copy font configuration files
+cp $prefix/etc/fonts/fonts.conf $bundle/Contents/Resources
+
+# Copy GTK and pango modules
+mkdir -p "$bundle/Contents/lib/modules"
+mkdir -p "$bundle/Contents/lib/gtk-2.0/engines"
+cp $prefix/lib/gtk-2.0/2.10.0/engines/libquartz.so $bundle/Contents/lib/gtk-2.0/engines
+cp $(find /usr/local/Cellar/pango -name '*basic-coretext*') $bundle/Contents/lib/modules
+
+# Copy GdkPixbuf loaders
+mkdir -p $bundle/Contents/lib/gdk-pixbuf-2.0/2.10.0/loaders/
+for fmt in icns png; do
+ cp $prefix/lib/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-$fmt.so \
+ $bundle/Contents/lib/gdk-pixbuf-2.0/2.10.0/loaders/;
+done
+
+chmod -R 755 $bundle/Contents/lib/*
+
+# Copy libraries depended on by the executable to bundle
+libs="`otool -L $exe | grep '\.dylib\|\.so' | grep '/User\|/usr/local' | sed 's/(.*//'`"
+for l in $libs; do
+ cp $l $bundle/Contents/lib/;
+done
+chmod 755 $bundle/Contents/lib/*
+
+# ... recursively
+while true; do
+ newlibs=$libs
+
+ # Copy all libraries this library depends on to bundle
+ for l in $(find $bundle -name '*.dylib' -or -name '*.so'); do
+ reclibs="`otool -L $l | grep '\.dylib\|\.so' | grep '/User\|/usr/local' | sed 's/(.*//'`"
+ for rl in $reclibs; do
+ cp $rl $bundle/Contents/lib/;
+ done
+ chmod 755 $bundle/Contents/lib/*
+ newlibs=$(echo "$newlibs"; echo "$reclibs")
+ done
+
+ # Exit once we haven't added any new libraries
+ newlibs=$(echo "$newlibs" | sort | uniq)
+ if [ "$newlibs" = "$libs" ]; then
+ break;
+ fi
+ libs=$newlibs
+done
+
+echo "Bundled libraries:"
+echo "$libs"
+
+for l in $libs; do
+ lname=`echo $l | sed 's/.*\///'`
+ lid="@executable_path/lib/$lname"
+ lpath="$bundle/Contents/lib/$lname"
+ install_name_tool -id $lid $lpath
+ install_name_tool -change $l $lid $exe
+ for j in `find $bundle -name '*.so' -or -name '*.dylib'`; do
+ install_name_tool -change $l $lid $j
+ done;
+done
+
+echo "External library references:"
+otool -L $exe `find $bundle -name '*.so' -or -name '*.dylib'` | grep -v ':' | grep -v '@executable_path' | sort | uniq
diff --git a/wscript b/wscript
index 9455c70..d21ea4b 100644
--- a/wscript
+++ b/wscript
@@ -237,6 +237,28 @@ def build(bld):
cxxflags = ['-fPIC', '-std=c++11'])
autowaf.use_lib(bld, obj, libs + ' QT5')
+ # Darwin version
+ if bld.env.DEST_OS == 'darwin':
+ obj = bld(features = 'c cxx cxxprogram',
+ source = source + ' src/jalv_osx.m',
+ target = 'Jalv.app/Contents/Jalv',
+ includes = ['.', 'src'],
+ lib = ['pthread'],
+ framework = ['Cocoa'],
+ install_path = '${BINDIR}')
+ autowaf.use_lib(bld, obj, libs + ' GTKMM2')
+
+ for i in bld.path.ant_glob('src/osx/*.xib'):
+ bld(rule='ibtool --compile ${TGT} ${SRC}',
+ source = i,
+ target = 'Jalv.app/Contents/Resources/' + i.name.replace('.xib', '.nib'))
+
+ for i in bld.path.ant_glob('src/osx/*', resursive=True):
+ bld(features = 'subst',
+ is_copy = True,
+ source = i,
+ target = 'Jalv.app/Contents/Resources/' + i.name)
+
# Man pages
bld.install_files('${MANDIR}/man1', bld.path.ant_glob('doc/*.1'))
@@ -261,3 +283,9 @@ def posts(ctx):
{ 'Author' : 'drobilla',
'Tags' : 'Hacking, LAD, LV2, Jalv' },
os.path.join(out, 'posts'))
+
+# Alias .m files to be compiled the same as .c files, gcc will do the right thing.
+from waflib import TaskGen
+@TaskGen.extension('.m')
+def m_hook(self, node):
+ return self.create_compiled_task('c', node)