diff options
author | David Robillard <d@drobilla.net> | 2017-02-23 19:01:03 +0100 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2017-02-23 19:01:03 +0100 |
commit | c505e04d5b21a317f99c0a8459b090428e845d2e (patch) | |
tree | abd178d9f36e6cecd418a231908a3d040a80f247 | |
parent | 0b587a635b966bee3569fbd5569b091827e33ceb (diff) | |
download | jalv-c505e04d5b21a317f99c0a8459b090428e845d2e.tar.gz jalv-c505e04d5b21a317f99c0a8459b090428e845d2e.tar.bz2 jalv-c505e04d5b21a317f99c0a8459b090428e845d2e.zip |
Preliminary native OSX workosx
-rw-r--r-- | src/jalv.c | 428 | ||||
-rw-r--r-- | src/jalv_internal.h | 3 | ||||
-rw-r--r-- | src/jalv_osx.m | 159 | ||||
-rw-r--r-- | src/osx/Info.plist | 34 | ||||
-rw-r--r-- | src/osx/Jalv.icns | bin | 0 -> 202450 bytes | |||
-rw-r--r-- | src/osx/MainMenu.xib | 116 | ||||
-rw-r--r-- | src/osx/MainWindow.xib | 31 | ||||
-rw-r--r-- | src/osx/SelectPlugin.xib | 99 | ||||
-rwxr-xr-x | src/osx/bundleify.sh | 79 | ||||
-rw-r--r-- | wscript | 28 |
10 files changed, 773 insertions, 204 deletions
@@ -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 Binary files differnew file mode 100644 index 0000000..f3e02ef --- /dev/null +++ b/src/osx/Jalv.icns 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 @@ -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) |