From c505e04d5b21a317f99c0a8459b090428e845d2e Mon Sep 17 00:00:00 2001 From: David Robillard Date: Thu, 23 Feb 2017 19:01:03 +0100 Subject: Preliminary native OSX work --- src/jalv.c | 428 +++++++++++++++++++++++++---------------------- src/jalv_internal.h | 3 + src/jalv_osx.m | 159 ++++++++++++++++++ src/osx/Info.plist | 34 ++++ src/osx/Jalv.icns | Bin 0 -> 202450 bytes src/osx/MainMenu.xib | 116 +++++++++++++ src/osx/MainWindow.xib | 31 ++++ src/osx/SelectPlugin.xib | 99 +++++++++++ src/osx/bundleify.sh | 79 +++++++++ wscript | 28 ++++ 10 files changed, 773 insertions(+), 204 deletions(-) create mode 100644 src/jalv_osx.m create mode 100644 src/osx/Info.plist create mode 100644 src/osx/Jalv.icns create mode 100644 src/osx/MainMenu.xib create mode 100644 src/osx/MainWindow.xib create mode 100644 src/osx/SelectPlugin.xib create mode 100755 src/osx/bundleify.sh 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 +#import +#include "jalv_internal.h" + +@interface AppDelegate : NSObject +@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 +@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 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + Jalv + CFBundleExecutable + Jalv + CFBundleGetInfoString + Jalv, Copyright © 2016 David Robillard + CFBundleIconFile + Jalv + CFBundleIdentifier + net.drobilla.Jalv + CFBundleName + Jalv + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + djlv + LSApplicationCategoryType + public.app-category.music + NSHumanReadableCopyright + Copyright © 2016 David Robillard. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/src/osx/Jalv.icns b/src/osx/Jalv.icns new file mode 100644 index 0000000..f3e02ef Binary files /dev/null and b/src/osx/Jalv.icns 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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) -- cgit v1.2.1