diff options
-rw-r--r-- | doc/c/overview.rst | 189 | ||||
-rw-r--r-- | include/suil/suil.h | 50 | ||||
-rw-r--r-- | meson.build | 43 |
3 files changed, 253 insertions, 29 deletions
diff --git a/doc/c/overview.rst b/doc/c/overview.rst index e8d61b1..e6d4e63 100644 --- a/doc/c/overview.rst +++ b/doc/c/overview.rst @@ -1,6 +1,6 @@ -######## -Overview -######## +########## +Using Suil +########## .. default-domain:: c .. highlight:: c @@ -10,3 +10,186 @@ The complete API is declared in ``suil.h``: .. code-block:: c #include <suil/suil.h> + +************* +Library Setup +************* + +In order to work properly on certain platforms, +suil must be set up by the application before any GUI toolkits are loaded. +This is done with :func:`suil_init`, +which takes command line arguments and an optional list of arguments, +terminated by :enumerator:`SUIL_ARG_NONE`: + +.. code-block:: c + + int main(int argc, char** argv) + { + suil_init(&argc, &argv, SUIL_ARG_NONE); + return 0; + } + +There are currently no defined Suil arguments, +the facility exists for future extensibility. + +********** +Host Setup +********** + +After the library is prepared, +a :struct:`SuilHost` must be created. +This represents the plugin UI host, +in particular the callbacks that will fire when there is UI activity. +There are five types of host callback: + +- :type:`SuilPortWriteFunc` +- :type:`SuilPortIndexFunc` +- :type:`SuilPortSubscribeFunc` +- :type:`SuilPortUnsubscribeFunc` +- :type:`SuilTouchFunc` + +These callbacks all take a :type:`SuilController`, +which is an opaque pointer that can be used to describe the context +(including which UI is responsible for the call), +as well as extra arguments specific to the callback. + +Assuming these are implemented in the host as ``my_write_func``, ``my_port_index_func``, ``my_port_subscribe_func``, ``my_port_unsubscribe_func``, and ``my_touch_func``, respectively, +the host can be allocated with :func:`suil_host_new`: + +.. code-block:: c + + SuilHost* host = suil_host_new(my_write_func, + my_index_func, + my_subscribe_func, + my_unsubscribe_func); + +Touch functionality was added to the API later, +so the touch function is set with the separate :func:`suil_host_set_touch_func`: + +.. code-block:: c + + suil_host_set_touch_func(host, my_touch_func); + +********************* +Finding Supported UIs +********************* + +Suil does not access data directly itself, +so UI discovery is the responsibility of the application. +Most hosts will use :func:`lilv_plugin_get_uis` to discover the available UIs for a particular plugin. + +Suil provides :func:`suil_ui_supported`, +which can be used to determine if a particular UI can be embedded in the host. +It requires two URIs, +one that describes the host UI type, +and one that describes the plugin UI type. +They are typically URIs defined in the LV2 UI extension, +such as https://lv2plug.in/ns/extensions/ui#X11UI, +which are defined for convenience with names like :var:`LV2_UI__X11UI`. + +If a specific UI type is already known, +this can be used directly, +for example an X11 host might do something like this: + +.. code-block:: c + + if (suil_ui_supported(LV2_UI__X11UI, plugin_ui_type) > 0) { + // Use this UI... + } + +More commonly, +this function is used as a utility along with :func:`lilv_ui_is_supported`: + +.. code-block:: c + + LilvUI* ui = this_plugin_ui(); + LilvNode* host_type = lilv_new_uri(LV2_UI__X11UI); + LilvNode* ui_type = NULL; + + if (lilv_ui_is_supported(ui, suil_ui_supported, host_type, &ui_type)) { + // Use this UI, which has type ui_type... + } + +********************* +Loading a UI Instance +********************* + +An instance of a plugin UI is represented by a :struct:`SuilInstance`, +which can be created with :func:`suil_instance_new`. +For example, +given a :struct:`LilvPlugin` ``plugin``, +and a :struct:`LilvUI` ``ui``: + +.. code-block:: c + + SuilController* controller = my_controller_for(ui); + const LV2_Feature** features = my_features_for(ui); + + const char* bundle_uri = lilv_node_as_uri(lilv_ui_get_bundle_uri(ui)); + const char* binary_uri = lilv_node_as_uri(lilv_ui_get_binary_uri(ui)); + char* bundle_path = serd_parse_file_uri(bundle_uri, NULL); + char* binary_path = serd_parse_file_uri(binary_uri, NULL); + + SuilInstance* instance = suil_instance_new( + host, + controller, + LV2_UI__X11UI, + lilv_node_as_uri(lilv_plugin_get_uri(plugin)), + lilv_node_as_uri(lilv_ui_get_uri(ui)), + lilv_node_as_uri(ui_type), + bundle_path, + binary_path, + ui_features); + +********************* +Retrieving the Widget +********************* + +With the UI instantiated, +the actual widget can be retrieved with :func:`suil_instance_get_widget`. +This returns a :type:`SuilWidget` handle which has the container type passed to :func:`suil_instance_new`. +In this example, +the host is a LV2_UI__X11UI, +so the returned widget is a ``Window``: + +.. code-block:: c + + Window window = (Window)suil_instance_get_widget(instance); + +The host may be responsible for displaying or embedding the widget, +depending on the windowing system or toolkit used. + +*************** +Updating the UI +*************** + +Updates can be sent to a UI instance with :func:`suil_instance_port_event`. +This takes a port index, a value size and format, and a pointer to a value. +For example, to update a control port, the special format ``0`` can be used: + +.. code-block:: c + + const float value = 42.0f; + + suil_instance_port_event(instance, 2, sizeof(float), 0, &value); + +******* +Cleanup +******* + +When the host is finished with the UI, +and has removed any references to it in the windowing system or toolkit, +it must destroy the UI with :func:`suil_instance_free`: + +.. code-block:: c + + suil_instance_free(instance); + +When all UIs have been destroyed and the host is ready to shut down, +it must free its description with :func:`suil_host_free`: + +.. code-block:: c + + suil_host_free(host); + +Note that the host must outlive any UI instances that were created for it. diff --git a/include/suil/suil.h b/include/suil/suil.h index ec2ea5e..779a169 100644 --- a/include/suil/suil.h +++ b/include/suil/suil.h @@ -1,5 +1,5 @@ /* - Copyright 2011-2017 David Robillard <d@drobilla.net> + Copyright 2011-2021 David Robillard <d@drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -47,27 +47,14 @@ extern "C" { #endif /** - @defgroup suil Suil + @defgroup suil Suil C API @{ */ /** - UI host descriptor. - - This contains the various functions that a plugin UI may use to communicate - with the plugin. It is passed to suil_instance_new() to provide these - functions to the UI. + @defgroup suil_host UI Host + @{ */ -typedef struct SuilHostImpl SuilHost; - -/// An instance of an LV2 plugin UI -typedef struct SuilInstanceImpl SuilInstance; - -/// Opaque pointer to a UI handle -typedef void* SuilHandle; - -/// Opaque pointer to a UI widget -typedef void* SuilWidget; /** UI controller. @@ -112,6 +99,15 @@ typedef void (*SuilTouchFunc)( // uint32_t port_index, bool grabbed); +/** + UI host descriptor. + + This contains the various functions that a plugin UI may use to communicate + with the plugin. It is passed to suil_instance_new() to provide these + functions to the UI. +*/ +typedef struct SuilHostImpl SuilHost; + /// Initialization argument typedef enum { SUIL_ARG_NONE } SuilArg; @@ -159,6 +155,21 @@ void suil_host_free(SuilHost* host); /** + @} + @defgroup suil_ui UI Instances + @{ +*/ + +/// An instance of an LV2 plugin UI +typedef struct SuilInstanceImpl SuilInstance; + +/// Opaque pointer to a UI handle +typedef void* SuilHandle; + +/// Opaque pointer to a UI widget +typedef void* SuilWidget; + +/** Check if suil can wrap a UI type. @param host_type_uri The URI of the desired widget type of the host, @@ -224,8 +235,8 @@ suil_instance_free(SuilInstance* instance); Get the handle for a UI instance. Returns the handle to the UI instance. The returned handle has opaque type - to insulate the Suil API from LV2 extensions, but in pactice it is currently - of type `LV2UI_Handle`. This should not normally be needed. + to insulate the Suil API from LV2 extensions, but in practice it is + currently of type `LV2UI_Handle`. This should not normally be needed. The returned handle is shared and must not be deleted. */ @@ -277,6 +288,7 @@ suil_instance_extension_data(SuilInstance* instance, const char* uri); /** @} + @} */ #ifdef __cplusplus diff --git a/meson.build b/meson.build index 7a413ff..02e83b8 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,7 @@ if get_option('strict') elif cc.get_id() == 'gcc' c_warnings += [ '-Wno-padded', + '-Wno-pragmas', '-Wno-suggest-attribute=const', '-Wno-suggest-attribute=pure', '-Wno-extra-semi', # Qt headers @@ -56,9 +57,6 @@ if get_option('strict') ] endif - add_project_arguments(cc.get_supported_arguments(c_warnings), - language: ['c']) - cpp_warnings = all_cpp_warnings if cpp.get_id() == 'clang' cpp_warnings += [ @@ -75,6 +73,7 @@ if get_option('strict') cpp_warnings += [ '-Wno-cast-qual', '-Wno-padded', + '-Wno-pragmas', '-Wno-suggest-attribute=const', '-Wno-suggest-attribute=pure', '-Wno-useless-cast', @@ -82,10 +81,36 @@ if get_option('strict') ] endif - add_project_arguments(cpp.get_supported_arguments(cpp_warnings), - language: ['cpp']) +else + c_warnings = [] + if cc.get_id() == 'clang' + c_warnings += [ + '-Wno-pragmas', + ] + elif cc.get_id() == 'gcc' + c_warnings += [ + '-Wno-pragmas', + ] + endif + + cpp_warnings = [] + if cpp.get_id() == 'clang' + cpp_warnings += [ + '-Wno-pragmas', + ] + elif cpp.get_id() == 'gcc' + cpp_warnings += [ + '-Wno-pragmas', + ] + endif endif +add_project_arguments(cc.get_supported_arguments(c_warnings), + language: ['c']) + +add_project_arguments(cpp.get_supported_arguments(cpp_warnings), + language: ['cpp']) + # Add special arguments for MSVC if cc.get_id() == 'msvc' msvc_args = [ @@ -112,6 +137,8 @@ dl_dep = cc.find_library('dl', required: false) # Dependencies +gtk2_quartz_dep = disabler() + lv2_dep = dependency('lv2', version: '>= 1.18.3', fallback: ['lv2', 'lv2_dep']) @@ -119,7 +146,9 @@ lv2_dep = dependency('lv2', x11_dep = dependency('x11', required: false) gtk2_dep = dependency('gtk+-2.0', version: '>=2.18.0', required: false)#, include_type: 'system') gtk2_x11_dep = dependency('gtk+-x11-2.0', required: false)#, include_type: 'system') -gtk2_quartz_dep = dependency('gtk+-quartz-2.0', required: false)#, include_type: 'system') +if host_machine.system() == 'darwin' + gtk2_quartz_dep = dependency('gtk+-quartz-2.0', required: false)#, include_type: 'system') +endif gtk3_dep = dependency('gtk+-3.0', version: '>=3.14.0', required: false)#, include_type: 'system') gtk3_x11_dep = dependency('gtk+-x11-3.0', version: '>=3.14.0', required: false)#, include_type: 'system') qt5_dep = dependency('Qt5Widgets', version: '>=5.1.0', required: false)#, include_type: 'system') @@ -252,7 +281,7 @@ if qt5_dep.found() and qt5_x11_dep.found() endif -if meson.version().version_compare('>=0.53.0') +if not meson.is_subproject() and meson.version().version_compare('>=0.53.0') # summary('Tests', get_option('tests'), bool_yn: true) summary('Install prefix', get_option('prefix')) |