aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml72
-rw-r--r--CMakeLists.txt153
-rw-r--r--COPYING201
-rw-r--r--README.md43
-rw-r--r--VERSION1
-rw-r--r--atom_inspector.c234
-rw-r--r--atom_inspector_nk.c278
-rw-r--r--cmake/arm-linux-gnueabihf.cmake22
-rw-r--r--cmake/i686-linux-gnu.cmake11
-rw-r--r--cmake/i686-w64-mingw32.cmake25
-rw-r--r--cmake/universal-apple-darwin.cmake24
-rw-r--r--cmake/x86_64-linux-gnu.cmake8
-rw-r--r--cmake/x86_64-w64-mingw32.cmake25
-rw-r--r--encoder.l298
-rw-r--r--manifest.ttl.in62
-rw-r--r--midi_inspector.c253
-rw-r--r--midi_inspector_nk.c505
-rw-r--r--nk_pugl/COPYING201
-rw-r--r--nk_pugl/nk_pugl.h953
-rw-r--r--nuklear/.gitattributes (renamed from .gitattributes)0
-rw-r--r--nuklear/.gitignore (renamed from .gitignore)0
-rw-r--r--nuklear/.travis.yml (renamed from .travis.yml)0
-rw-r--r--nuklear/CHANGELOG.md (renamed from CHANGELOG.md)0
-rw-r--r--nuklear/Readme.md (renamed from Readme.md)0
-rw-r--r--nuklear/demo/calculator.c (renamed from demo/calculator.c)0
-rw-r--r--nuklear/demo/d3d11/build.bat (renamed from demo/d3d11/build.bat)0
-rw-r--r--nuklear/demo/d3d11/main.c (renamed from demo/d3d11/main.c)0
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11.h (renamed from demo/d3d11/nuklear_d3d11.h)0
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11.hlsl (renamed from demo/d3d11/nuklear_d3d11.hlsl)0
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h (renamed from demo/d3d11/nuklear_d3d11_pixel_shader.h)0
-rw-r--r--nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h (renamed from demo/d3d11/nuklear_d3d11_vertex_shader.h)0
-rw-r--r--nuklear/demo/gdi/build.bat (renamed from demo/gdi/build.bat)0
-rw-r--r--nuklear/demo/gdi/main.c (renamed from demo/gdi/main.c)0
-rw-r--r--nuklear/demo/gdi/nuklear_gdi.h (renamed from demo/gdi/nuklear_gdi.h)0
-rw-r--r--nuklear/demo/gdip/build.bat (renamed from demo/gdip/build.bat)0
-rw-r--r--nuklear/demo/gdip/main.c (renamed from demo/gdip/main.c)0
-rw-r--r--nuklear/demo/gdip/nuklear_gdip.h (renamed from demo/gdip/nuklear_gdip.h)0
-rw-r--r--nuklear/demo/glfw_opengl2/Makefile (renamed from demo/glfw_opengl2/Makefile)0
-rw-r--r--nuklear/demo/glfw_opengl2/main.c (renamed from demo/glfw_opengl2/main.c)0
-rw-r--r--nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h (renamed from demo/glfw_opengl2/nuklear_glfw_gl2.h)0
-rw-r--r--nuklear/demo/glfw_opengl3/Makefile (renamed from demo/glfw_opengl3/Makefile)0
-rw-r--r--nuklear/demo/glfw_opengl3/main.c (renamed from demo/glfw_opengl3/main.c)0
-rw-r--r--nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h (renamed from demo/glfw_opengl3/nuklear_glfw_gl3.h)0
-rw-r--r--nuklear/demo/node_editor.c (renamed from demo/node_editor.c)0
-rw-r--r--nuklear/demo/overview.c (renamed from demo/overview.c)0
-rw-r--r--nuklear/demo/sdl_opengl2/Makefile (renamed from demo/sdl_opengl2/Makefile)0
-rw-r--r--nuklear/demo/sdl_opengl2/main.c (renamed from demo/sdl_opengl2/main.c)0
-rw-r--r--nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h (renamed from demo/sdl_opengl2/nuklear_sdl_gl2.h)0
-rw-r--r--nuklear/demo/sdl_opengl3/Makefile (renamed from demo/sdl_opengl3/Makefile)0
-rw-r--r--nuklear/demo/sdl_opengl3/main.c (renamed from demo/sdl_opengl3/main.c)0
-rw-r--r--nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h (renamed from demo/sdl_opengl3/nuklear_sdl_gl3.h)0
-rw-r--r--nuklear/demo/style.c (renamed from demo/style.c)0
-rw-r--r--nuklear/demo/x11/Makefile (renamed from demo/x11/Makefile)0
-rw-r--r--nuklear/demo/x11/main.c (renamed from demo/x11/main.c)0
-rw-r--r--nuklear/demo/x11/nuklear_xlib.h (renamed from demo/x11/nuklear_xlib.h)0
-rw-r--r--nuklear/demo/x11_opengl2/Makefile (renamed from demo/x11_opengl2/Makefile)0
-rw-r--r--nuklear/demo/x11_opengl2/main.c (renamed from demo/x11_opengl2/main.c)0
-rw-r--r--nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h (renamed from demo/x11_opengl2/nuklear_xlib_gl2.h)0
-rw-r--r--nuklear/demo/x11_opengl3/Makefile (renamed from demo/x11_opengl3/Makefile)0
-rw-r--r--nuklear/demo/x11_opengl3/main.c (renamed from demo/x11_opengl3/main.c)0
-rw-r--r--nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h (renamed from demo/x11_opengl3/nuklear_xlib_gl3.h)0
-rw-r--r--nuklear/example/Makefile (renamed from example/Makefile)0
-rw-r--r--nuklear/example/canvas.c (renamed from example/canvas.c)0
-rw-r--r--nuklear/example/extended.c (renamed from example/extended.c)0
-rw-r--r--nuklear/example/file_browser.c (renamed from example/file_browser.c)0
-rw-r--r--nuklear/example/icon/checked.png (renamed from example/icon/checked.png)bin1813 -> 1813 bytes
-rw-r--r--nuklear/example/icon/cloud.png (renamed from example/icon/cloud.png)bin7509 -> 7509 bytes
-rw-r--r--nuklear/example/icon/computer.png (renamed from example/icon/computer.png)bin620 -> 620 bytes
-rw-r--r--nuklear/example/icon/copy.png (renamed from example/icon/copy.png)bin655 -> 655 bytes
-rw-r--r--nuklear/example/icon/default.png (renamed from example/icon/default.png)bin460 -> 460 bytes
-rw-r--r--nuklear/example/icon/delete.png (renamed from example/icon/delete.png)bin11040 -> 11040 bytes
-rw-r--r--nuklear/example/icon/desktop.png (renamed from example/icon/desktop.png)bin583 -> 583 bytes
-rw-r--r--nuklear/example/icon/directory.png (renamed from example/icon/directory.png)bin533 -> 533 bytes
-rw-r--r--nuklear/example/icon/edit.png (renamed from example/icon/edit.png)bin14998 -> 14998 bytes
-rw-r--r--nuklear/example/icon/export.png (renamed from example/icon/export.png)bin13336 -> 13336 bytes
-rw-r--r--nuklear/example/icon/font.png (renamed from example/icon/font.png)bin561 -> 561 bytes
-rw-r--r--nuklear/example/icon/home.png (renamed from example/icon/home.png)bin819 -> 819 bytes
-rw-r--r--nuklear/example/icon/img.png (renamed from example/icon/img.png)bin648 -> 648 bytes
-rw-r--r--nuklear/example/icon/movie.png (renamed from example/icon/movie.png)bin626 -> 626 bytes
-rw-r--r--nuklear/example/icon/music.png (renamed from example/icon/music.png)bin610 -> 610 bytes
-rw-r--r--nuklear/example/icon/next.png (renamed from example/icon/next.png)bin703 -> 703 bytes
-rw-r--r--nuklear/example/icon/pause.png (renamed from example/icon/pause.png)bin1338 -> 1338 bytes
-rw-r--r--nuklear/example/icon/pen.png (renamed from example/icon/pen.png)bin5949 -> 5949 bytes
-rw-r--r--nuklear/example/icon/phone.png (renamed from example/icon/phone.png)bin15778 -> 15778 bytes
-rw-r--r--nuklear/example/icon/plane.png (renamed from example/icon/plane.png)bin13546 -> 13546 bytes
-rw-r--r--nuklear/example/icon/play.png (renamed from example/icon/play.png)bin566 -> 566 bytes
-rw-r--r--nuklear/example/icon/prev.png (renamed from example/icon/prev.png)bin701 -> 701 bytes
-rw-r--r--nuklear/example/icon/rocket.png (renamed from example/icon/rocket.png)bin1121 -> 1121 bytes
-rw-r--r--nuklear/example/icon/settings.png (renamed from example/icon/settings.png)bin15671 -> 15671 bytes
-rw-r--r--nuklear/example/icon/stop.png (renamed from example/icon/stop.png)bin520 -> 520 bytes
-rw-r--r--nuklear/example/icon/text.png (renamed from example/icon/text.png)bin601 -> 601 bytes
-rw-r--r--nuklear/example/icon/tools.png (renamed from example/icon/tools.png)bin24483 -> 24483 bytes
-rw-r--r--nuklear/example/icon/unchecked.png (renamed from example/icon/unchecked.png)bin1044 -> 1044 bytes
-rw-r--r--nuklear/example/icon/volume.png (renamed from example/icon/volume.png)bin25438 -> 25438 bytes
-rw-r--r--nuklear/example/icon/wifi.png (renamed from example/icon/wifi.png)bin18857 -> 18857 bytes
-rw-r--r--nuklear/example/images/image1.png (renamed from example/images/image1.png)bin42882 -> 42882 bytes
-rw-r--r--nuklear/example/images/image2.png (renamed from example/images/image2.png)bin5671 -> 5671 bytes
-rw-r--r--nuklear/example/images/image3.png (renamed from example/images/image3.png)bin131502 -> 131502 bytes
-rw-r--r--nuklear/example/images/image4.png (renamed from example/images/image4.png)bin185821 -> 185821 bytes
-rw-r--r--nuklear/example/images/image5.png (renamed from example/images/image5.png)bin98475 -> 98475 bytes
-rw-r--r--nuklear/example/images/image6.png (renamed from example/images/image6.png)bin35633 -> 35633 bytes
-rw-r--r--nuklear/example/images/image7.png (renamed from example/images/image7.png)bin13960 -> 13960 bytes
-rw-r--r--nuklear/example/images/image8.png (renamed from example/images/image8.png)bin45987 -> 45987 bytes
-rw-r--r--nuklear/example/images/image9.png (renamed from example/images/image9.png)bin30759 -> 30759 bytes
-rw-r--r--nuklear/example/skinning.c (renamed from example/skinning.c)0
-rw-r--r--nuklear/example/skins/gwen.png (renamed from example/skins/gwen.png)bin24565 -> 24565 bytes
-rw-r--r--nuklear/example/stb_image.h (renamed from example/stb_image.h)0
-rw-r--r--nuklear/extra_font/Cousine-Regular.ttf (renamed from extra_font/Cousine-Regular.ttf)bin43912 -> 43912 bytes
-rw-r--r--nuklear/extra_font/DroidSans.ttf (renamed from extra_font/DroidSans.ttf)bin190044 -> 190044 bytes
-rw-r--r--nuklear/extra_font/Karla-Regular.ttf (renamed from extra_font/Karla-Regular.ttf)bin16848 -> 16848 bytes
-rw-r--r--nuklear/extra_font/ProggyClean.ttf (renamed from extra_font/ProggyClean.ttf)bin41208 -> 41208 bytes
-rw-r--r--nuklear/extra_font/ProggyTiny.ttf (renamed from extra_font/ProggyTiny.ttf)bin35656 -> 35656 bytes
-rw-r--r--nuklear/extra_font/Raleway-Bold.ttf (renamed from extra_font/Raleway-Bold.ttf)bin176280 -> 176280 bytes
-rw-r--r--nuklear/extra_font/Roboto-Bold.ttf (renamed from extra_font/Roboto-Bold.ttf)bin135820 -> 135820 bytes
-rw-r--r--nuklear/extra_font/Roboto-Light.ttf (renamed from extra_font/Roboto-Light.ttf)bin140276 -> 140276 bytes
-rw-r--r--nuklear/extra_font/Roboto-Regular.ttf (renamed from extra_font/Roboto-Regular.ttf)bin145348 -> 145348 bytes
-rw-r--r--nuklear/extra_font/kenvector_future.ttf (renamed from extra_font/kenvector_future.ttf)bin34136 -> 34136 bytes
-rw-r--r--nuklear/extra_font/kenvector_future_thin.ttf (renamed from extra_font/kenvector_future_thin.ttf)bin34100 -> 34100 bytes
-rw-r--r--nuklear/nuklear.h (renamed from nuklear.h)0
-rw-r--r--nuklear/package.json (renamed from package.json)0
-rw-r--r--osc.lv2/CMakeLists.txt16
-rw-r--r--osc.lv2/COPYING201
-rw-r--r--osc.lv2/README.md3
-rw-r--r--osc.lv2/lv2-osc.doap.ttl40
-rw-r--r--osc.lv2/manifest.ttl23
-rw-r--r--osc.lv2/osc.lv2/endian.h129
-rw-r--r--osc.lv2/osc.lv2/forge.h474
-rw-r--r--osc.lv2/osc.lv2/osc.h192
-rw-r--r--osc.lv2/osc.lv2/reader.h570
-rw-r--r--osc.lv2/osc.lv2/util.h481
-rw-r--r--osc.lv2/osc.lv2/writer.h557
-rw-r--r--osc.lv2/osc.ttl42
-rw-r--r--osc.lv2/osc_test.c423
-rw-r--r--osc_inspector.c255
-rw-r--r--osc_inspector_nk.c397
-rw-r--r--props.lv2/CMakeLists.txt41
-rw-r--r--props.lv2/COPYING201
-rw-r--r--props.lv2/README.md20
-rw-r--r--props.lv2/props.h1453
-rw-r--r--props.lv2/test/chunk.binbin0 -> 16 bytes
-rw-r--r--props.lv2/test/manifest.ttl.in28
-rw-r--r--props.lv2/test/props.c580
-rw-r--r--props.lv2/test/props.ttl152
-rw-r--r--pugl/.gitignore3
-rw-r--r--pugl/AUTHORS11
-rw-r--r--pugl/COPYING13
-rw-r--r--pugl/Doxyfile.in2415
-rw-r--r--pugl/README.md28
-rw-r--r--pugl/pugl.pc.in10
-rw-r--r--pugl/pugl/cairo_gl.h105
-rw-r--r--pugl/pugl/gl.h32
-rw-r--r--pugl/pugl/glew.h32
-rw-r--r--pugl/pugl/glu.h32
-rw-r--r--pugl/pugl/pugl.h622
-rw-r--r--pugl/pugl/pugl.hpp106
-rw-r--r--pugl/pugl/pugl_internal.h241
-rw-r--r--pugl/pugl/pugl_osx.m648
-rw-r--r--pugl/pugl/pugl_win.cpp644
-rw-r--r--pugl/pugl/pugl_x11.c721
-rw-r--r--pugl/pugl_cairo_test.c205
-rw-r--r--pugl/pugl_test.c261
-rwxr-xr-xpugl/wafbin0 -> 97489 bytes
-rw-r--r--pugl/wscript203
-rw-r--r--sherlock.c39
-rw-r--r--sherlock.h98
-rw-r--r--sherlock.ttl247
-rw-r--r--sherlock_nk.c569
-rw-r--r--sherlock_nk.h148
-rw-r--r--sherlock_ui.ttl59
169 files changed, 17139 insertions, 0 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..0bd9523
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,72 @@
+stages:
+ - build
+ - deploy
+
+.variables_template: &variables_definition
+ variables:
+ BASE_NAME: "sherlock.lv2"
+ PKG_CONFIG_PATH: "/opt/lv2/lib/pkgconfig:/opt/${CI_BUILD_NAME}/lib/pkgconfig:/usr/lib/${CI_BUILD_NAME}/pkgconfig"
+ TOOLCHAIN_FILE: "${CI_PROJECT_DIR}/cmake/${CI_BUILD_NAME}.cmake"
+
+.common_template: &common_definition
+ <<: *variables_definition
+ stage: build
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)-${CI_BUILD_NAME}"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
+
+.build_template: &build_definition
+ <<: *common_definition
+ script:
+ - mkdir build
+ - pushd build
+ - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=${CI_PROJECT_DIR} -DPLUGIN_DEST="${BASE_NAME}-$(cat ../VERSION)/${CI_BUILD_NAME}/${BASE_NAME}" -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_FILE} ..
+ - cmake .. # needed for darwin
+ - make
+ - make install
+
+.universal_linux_template: &universal_linux_definition
+ image: ventosus/universal-linux-gnu
+ <<: *build_definition
+
+.arm_linux_template: &arm_linux_definition
+ image: ventosus/arm-linux-gnueabihf
+ <<: *build_definition
+
+.universal_w64_template: &universal_w64_definition
+ image: ventosus/universal-w64-mingw32
+ <<: *build_definition
+
+.universal_apple_template: &universal_apple_definition
+ image: ventosus/universal-apple-darwin
+ <<: *build_definition
+
+# building in docker
+x86_64-linux-gnu:
+ <<: *universal_linux_definition
+
+i686-linux-gnu:
+ <<: *universal_linux_definition
+
+arm-linux-gnueabihf:
+ <<: *arm_linux_definition
+
+x86_64-w64-mingw32:
+ <<: *universal_w64_definition
+
+i686-w64-mingw32:
+ <<: *universal_w64_definition
+
+universal-apple-darwin:
+ <<: *universal_apple_definition
+
+pack:
+ <<: *variables_definition
+ stage: deploy
+ script:
+ - echo 'packing up...'
+ artifacts:
+ name: "${BASE_NAME}-$(cat VERSION)"
+ paths:
+ - "${BASE_NAME}-$(cat VERSION)/"
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..22803e6
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,153 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(sherlock.lv2)
+
+include_directories(${PROJECT_SOURCE_DIR})
+include_directories(${PROJECT_SOURCE_DIR}/osc.lv2)
+include_directories(${PROJECT_SOURCE_DIR}/props.lv2)
+include_directories(${PROJECT_SOURCE_DIR}/pugl)
+
+set(CMAKE_FIND_FRAMEWORK FIRST)
+
+set(CMAKE_C_FLAGS "-fdata-sections -ffunction-sections ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-std=gnu11 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
+
+if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,defs ${CMAKE_MODULE_LINKER_FLAGS}")
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,nodelete ${CMAKE_MODULE_LINKER_FLAGS}")
+elseif(WIN32)
+ set(CMAKE_C_FLAGS "-mstackrealign ${CMAKE_C_FLAGS}")
+endif()
+
+if(APPLE)
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-dead_strip ${CMAKE_MODULE_LINKER_FLAGS}")
+else()
+ set(CMAKE_MODULE_LINKER_FLAGS "-Wl,--gc-sections -Wl,-s ${CMAKE_MODULE_LINKER_FLAGS}")
+endif()
+
+add_definitions("-D_GNU_SOURCE=1") # asprintf
+
+if(WIN32)
+ set(SHERLOCK_UI_TYPE "WindowsUI")
+elseif(APPLE)
+ set(SHERLOCK_UI_TYPE "CocoaUI")
+else()
+ set(SHERLOCK_UI_TYPE "X11UI")
+endif()
+
+file(STRINGS "VERSION" SHERLOCK_VERSION)
+string(REPLACE "." ";" VERSION_LIST ${SHERLOCK_VERSION})
+list(GET VERSION_LIST 0 SHERLOCK_MAJOR_VERSION)
+list(GET VERSION_LIST 1 SHERLOCK_MINOR_VERSION)
+list(GET VERSION_LIST 2 SHERLOCK_MICRO_VERSION)
+add_definitions("-DSHERLOCK_VERSION=\"${SHERLOCK_VERSION}\"")
+
+if(NOT DEFINED PLUGIN_DEST)
+ set(PLUGIN_DEST lib/lv2/sherlock.lv2)
+endif()
+
+find_package(PkgConfig) # ${PKG_CONFIG_FOUND}
+
+
+set(LIBS_UI m)
+
+pkg_search_module(LV2 REQUIRED lv2>=1.10)
+include_directories(${LV2_INCLUDE_DIRS})
+
+find_package(OpenGL)
+if(${OPENGL_FOUND})
+ set(LIBS_UI ${LIBS_UI} ${OPENGL_LIBRARIES})
+else() # try pkg-config
+ pkg_search_module(GL REQUIRED gl)
+ if(${GL_FOUND})
+ set(LIBS_UI ${LIBS_UI} ${GL_LDFLAGS})
+ else()
+ message(FATAL_ERROR "OpenGL not found")
+ endif()
+endif()
+add_definitions("-DPUGL_HAVE_GL")
+
+if(WIN32)
+ find_library(GDI32_LIBRARY NAMES gdi32)
+ if(GDI32_LIBRARY)
+ set(LIBS_UI ${LIBS_UI} ${GDI32_LIBRARY})
+ else()
+ message(FATAL_ERROR "gdi32 library not found")
+ endif()
+
+ find_library(USER32_LIBRARY NAMES user32)
+ if(USER32_LIBRARY)
+ set(LIBS_UI ${LIBS_UI} ${USER32_LIBRARY})
+ else()
+ message(FATAL_ERROR "user32 library not found")
+ endif()
+
+ set(TAR_UI ${TAR_UI} pugl/pugl/pugl_win.cpp)
+
+elseif(APPLE)
+ find_library(COCOA_LIBRARY NAMES Cocoa)
+ if(COCOA_LIBRARY)
+ set(LIBS_UI ${LIBS_UI} ${COCOA_LIBRARY})
+ else()
+ message(FATAL_ERROR "Cocoa framework not found")
+ endif()
+
+ set(TAR_UI ${TAR_UI} pugl/pugl/pugl_osx.m)
+
+else() # GNU/Linux
+ pkg_search_module(X11 REQUIRED x11>=1.6)
+ include_directories(${X11_INCLUDE_DIRS})
+ set(LIBS_UI ${LIBS_UI} ${X11_LDFLAGS})
+
+ pkg_search_module(XEXT REQUIRED xext>=1.3)
+ include_directories(${XEXT_INCLUDE_DIRS})
+ set(LIBS_UI ${LIBS_UI} ${XEXT_LDFLAGS})
+
+ set(TAR_UI ${TAR_UI} pugl/pugl/pugl_x11.c)
+endif()
+
+pkg_search_module(SRATOM REQUIRED sratom-0>=0.4.0)
+include_directories(${SRATOM_INCLUDE_DIRS})
+if(DEFINED STATIC_SRATOM)
+ set(LIBS_UI ${STATIC_SRATOM} ${STATIC_SORD} ${STATIC_SERD} ${LIBS_UI})
+else()
+ set(LIBS_UI ${LIBS_UI} ${SRATOM_LDFLAGS})
+endif()
+
+
+add_library(sherlock MODULE
+ sherlock.c
+ atom_inspector.c
+ midi_inspector.c
+ osc_inspector.c)
+set_target_properties(sherlock PROPERTIES PREFIX "")
+if(NOT WIN32)
+ set_target_properties(sherlock PROPERTIES LINK_FLAGS "-Wl,-e,lv2_descriptor")
+endif()
+install(TARGETS sherlock DESTINATION ${PLUGIN_DEST})
+
+#find_package(FLEX)
+#flex_target(encoder encoder.l ${PROJECT_BINARY_DIR}/encoder.c
+# COMPILE_FLAGS "--header-file=${PROJECT_BINARY_DIR}/encoder.h --prefix=enc")
+
+add_library(sherlock_nk MODULE
+ sherlock_nk.c
+ midi_inspector_nk.c
+ atom_inspector_nk.c
+ osc_inspector_nk.c
+ ${TAR_UI})
+target_link_libraries(sherlock_nk
+ ${LIBS_UI})
+set_target_properties(sherlock_nk PROPERTIES PREFIX "")
+if(NOT WIN32)
+ set_target_properties(sherlock_nk PROPERTIES LINK_FLAGS "-Wl,-e,lv2ui_descriptor")
+endif()
+install(TARGETS sherlock_nk DESTINATION ${PLUGIN_DEST})
+
+configure_file(${PROJECT_SOURCE_DIR}/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
+install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${PLUGIN_DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/sherlock.ttl DESTINATION ${PLUGIN_DEST})
+install(FILES ${PROJECT_SOURCE_DIR}/sherlock_ui.ttl DESTINATION ${PLUGIN_DEST})
+
+install(FILES ${PROJECT_SOURCE_DIR}/nuklear/extra_font/Cousine-Regular.ttf DESTINATION ${PLUGIN_DEST})
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2c57307
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# Sherlock
+
+## An investigative LV2 plugin bundle
+
+### Webpage
+
+Get more information at: [http://open-music-kontrollers.ch/lv2/sherlock](http://open-music-kontrollers.ch/lv2/sherlock)
+
+### Build status
+
+[![build status](https://gitlab.com/OpenMusicKontrollers/sherlock.lv2/badges/master/build.svg)](https://gitlab.com/OpenMusicKontrollers/sherlock.lv2/commits/master)
+
+### Dependencies
+
+* [LV2](http://lv2plug.in) (LV2 Plugin Standard)
+* [pugl](http://drobilla.net/software/pugl) (Portable API for OpenGL GUIs)
+* [nuklear](https://github.com/vurtun/nuklear) (Immediate-mode GUI)
+
+### Build / install
+
+ git clone https://github.com/OpenMusicKontrollers/sherlock.lv2.git
+ cd sherlock.lv2
+ mkdir build
+ cd build
+ make
+ sudo make install
+
+### License
+
+Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..86fe05b
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+0.11.2731
diff --git a/atom_inspector.c b/atom_inspector.c
new file mode 100644
index 0000000..9e71b43
--- /dev/null
+++ b/atom_inspector.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sherlock.h>
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ const LV2_Atom_Sequence *control_in;
+ LV2_Atom_Sequence *control_out;
+ LV2_Atom_Sequence *notify;
+ LV2_Atom_Forge forge;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+
+ int64_t frame;
+
+ PROPS_T(props, MAX_NPROPS);
+ state_t state;
+ state_t stash;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(i=0; features[i]; i++)
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = (LV2_URID_Map *)features[i]->data;
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ if( !props_register(&handle->props, &stat_overwrite, &handle->state.overwrite, &handle->stash.overwrite)
+ || !props_register(&handle->props, &stat_block, &handle->state.block, &handle->stash.block)
+ || !props_register(&handle->props, &stat_follow, &handle->state.follow, &handle->stash.follow) )
+ {
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->control_out = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ uint32_t capacity;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+
+ // size of input sequence
+ const size_t size = lv2_atom_total_size(&handle->control_in->atom);
+
+ capacity = handle->control_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity);
+ ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ // copy all events to through port
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, &obj->atom, lv2_atom_total_size(&obj->atom));
+ if(ref)
+ lv2_atom_forge_pad(forge, obj->atom.size);
+
+ if( !props_advance(&handle->props, forge, frames, obj, &ref)
+ && lv2_atom_forge_is_object_type(forge, obj->atom.type)
+ && (obj->body.otype == handle->time_position) )
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - frames;
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->control_out);
+
+ // forge whole sequence as single event
+ capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity);
+ ref = lv2_atom_forge_sequence_head(forge, &frame, 0);
+
+ // only serialize sequence to UI if there were actually any events
+ if(handle->control_in->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ {
+ LV2_Atom_Forge_Frame tup_frame;
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &tup_frame);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, handle->frame);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, handle->control_in, size);
+ if(ref)
+ lv2_atom_forge_pop(forge, &tup_frame);
+
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ handle_t *handle = instance;
+
+ return props_save(&handle->props, &handle->forge, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ handle_t *handle = instance;
+
+ return props_restore(&handle->props, &handle->forge, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+ return NULL;
+}
+
+const LV2_Descriptor atom_inspector = {
+ .URI = SHERLOCK_ATOM_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/atom_inspector_nk.c b/atom_inspector_nk.c
new file mode 100644
index 0000000..07b1a9e
--- /dev/null
+++ b/atom_inspector_nk.c
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <ctype.h>
+#include <inttypes.h>
+
+#include <sherlock.h>
+#include <sherlock_nk.h>
+
+#define NS_RDF (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RDFS (const uint8_t*)"http://www.w3.org/2000/01/rdf-schema#"
+#define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#"
+#define NS_OSC (const uint8_t*)"http://open-music-kontrollers.ch/lv2/osc#"
+#define NS_XPRESS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/xpress#"
+#define NS_SPOD (const uint8_t*)"http://open-music-kontrollers.ch/lv2/synthpod#"
+#define NS_CANVAS (const uint8_t*)"http://open-music-kontrollers.ch/lv2/canvas#"
+
+// copyied and adapted from libsratom
+static inline char *
+_sratom_to_turtle(Sratom* sratom,
+ LV2_URID_Unmap* unmap,
+ const char* base_uri,
+ const SerdNode* subject,
+ const SerdNode* predicate,
+ uint32_t type,
+ uint32_t size,
+ const void* body)
+{
+ SerdURI buri = SERD_URI_NULL;
+ SerdNode base = serd_node_new_uri_from_string((uint8_t *)(base_uri), NULL, &buri);
+ SerdEnv* env = serd_env_new(&base);
+ SerdChunk str = { NULL, 0 };
+
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdf", NS_RDF);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"rdfs", NS_RDFS);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"xsd", NS_XSD);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"lv2", (const uint8_t *)LV2_CORE_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"midi", (const uint8_t *)LV2_MIDI_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"atom", (const uint8_t *)LV2_ATOM_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"units", (const uint8_t *)LV2_UNITS_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"ui", (const uint8_t *)LV2_UI_PREFIX);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"time", (const uint8_t *)LV2_TIME_URI"#");
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"patch", (const uint8_t *)LV2_PATCH_PREFIX);
+
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"osc", NS_OSC);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"xpress", NS_XPRESS);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"spod", NS_SPOD);
+ serd_env_set_prefix_from_strings(env, (const uint8_t *)"canvas", NS_CANVAS);
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE,
+ (SerdStyle)(SERD_STYLE_ABBREVIATED |
+ SERD_STYLE_RESOLVED |
+ SERD_STYLE_CURIED |
+ SERD_STYLE_ASCII),
+ env, &buri, serd_chunk_sink, &str);
+
+ // Write @prefix directives
+ serd_env_foreach(env,
+ (SerdPrefixSink)serd_writer_set_prefix,
+ writer);
+
+ sratom_set_sink(sratom, base_uri,
+ (SerdStatementSink)serd_writer_write_statement,
+ (SerdEndSink)serd_writer_end_anon,
+ writer);
+ sratom_write(sratom, unmap, SERD_EMPTY_S,
+ subject, predicate, type, size, body);
+ serd_writer_finish(writer);
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+ serd_node_free(&base);
+ return (char*)serd_chunk_sink_finish(&str);
+}
+
+void
+_atom_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ const float widget_h = handle->dy;
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 window_padding = style->window.padding;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+
+ style->selectable.normal.data.color.a = 0x0;
+ style->selectable.hover.data.color.a = 0x0;
+
+ if(nk_begin(ctx, "Window", wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ nk_window_set_bounds(ctx, wbounds);
+ struct nk_panel *panel= nk_window_get_panel(ctx);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float body_h = panel->bounds.h - 2*window_padding.y;
+ nk_layout_row_dynamic(ctx, body_h, 2);
+ if(nk_group_begin(ctx, "Left", NK_WINDOW_NO_SCROLLBAR))
+ {
+ const float content_h = nk_window_get_height(ctx) - 2*window_padding.y - 4*group_padding.y - 2*widget_h;
+ nk_layout_row_dynamic(ctx, content_h, 1);
+ nk_flags flags = NK_WINDOW_BORDER;
+ if(handle->state.follow)
+ flags |= NK_WINDOW_NO_SCROLLBAR;
+ struct nk_list_view lview;
+ if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
+ {
+ if(handle->state.follow)
+ {
+ lview.end = NK_MAX(handle->n_item, 0);
+ lview.begin = NK_MAX(lview.end - lview.count, 0);
+ }
+ for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
+ {
+ item_t *itm = handle->items[l];
+
+ switch(itm->type)
+ {
+ case ITEM_TYPE_NONE:
+ {
+ // skip, never reached
+ } break;
+ case ITEM_TYPE_FRAME:
+ {
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 3;
+ b.w += 4*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
+ nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
+ } break;
+
+ case ITEM_TYPE_EVENT:
+ {
+ LV2_Atom_Event *ev = &itm->event.ev;
+ const LV2_Atom *body = &ev->body;
+ const int64_t frames = ev->time.frames;
+ const char *uri = NULL;
+ if(lv2_atom_forge_is_object_type(&handle->forge, body->type))
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)body;
+
+ if(obj->body.otype)
+ uri = handle->unmap->unmap(handle->unmap->handle, obj->body.otype);
+ else if(obj->body.id)
+ uri = handle->unmap->unmap(handle->unmap->handle, obj->body.id);
+ else
+ uri = "Unknown";
+ }
+ else // not an object
+ {
+ uri = handle->unmap->unmap(handle->unmap->handle, body->type);
+ }
+
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 3);
+ {
+ nk_layout_row_push(ctx, 0.1);
+ if(l % 2 == 0)
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 10;
+ b.w += 8*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
+ }
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
+
+ nk_layout_row_push(ctx, 0.8);
+ if(nk_select_label(ctx, uri, NK_TEXT_LEFT, handle->selected == body))
+ {
+ handle->ttl_dirty = handle->ttl_dirty
+ || (handle->selected != body); // has selection actually changed?
+ handle->selected = body;
+ }
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size);
+ }
+ nk_layout_row_end(ctx);
+ } break;
+ }
+ }
+
+ nk_list_view_end(&lview);
+ }
+
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ if(nk_checkbox_label(ctx, "overwrite", &handle->state.overwrite))
+ _toggle(handle, handle->urid.overwrite, handle->state.overwrite, true);
+ if(nk_checkbox_label(ctx, "block", &handle->state.block))
+ _toggle(handle, handle->urid.block, handle->state.block, true);
+ if(nk_checkbox_label(ctx, "follow", &handle->state.follow))
+ _toggle(handle, handle->urid.follow, handle->state.follow, true);
+ }
+
+ const bool max_reached = handle->n_item >= MAX_LINES;
+ nk_layout_row_dynamic(ctx, widget_h, 2);
+ if(nk_button_symbol_label(ctx,
+ max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
+ "clear", NK_TEXT_LEFT))
+ {
+ _clear(handle);
+ }
+ nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
+
+ nk_group_end(ctx);
+ }
+
+ if(nk_group_begin(ctx, "Right", NK_WINDOW_NO_SCROLLBAR))
+ {
+ const LV2_Atom *atom = handle->selected;
+ if(handle->ttl_dirty && atom)
+ {
+ char *ttl = _sratom_to_turtle(handle->sratom, handle->unmap,
+ handle->base_uri, NULL, NULL,
+ atom->type, atom->size, LV2_ATOM_BODY_CONST(atom));
+ if(ttl)
+ {
+ struct nk_str *str = &handle->str;
+ const size_t len = strlen(ttl);
+
+ nk_str_clear(str);
+
+ char *from, *to;
+ for(from=ttl, to=strchr(from, '\t');
+ to;
+ from=to+1, to=strchr(from, '\t'))
+ {
+ nk_str_append_text_utf8(str, from, to-from);
+ nk_str_append_text_utf8(str, " ", 2);
+ }
+ nk_str_append_text_utf8(str, from, strlen(from));
+
+ free(ttl);
+ }
+
+ handle->ttl_dirty = false;
+ }
+
+ const nk_flags flags = NK_EDIT_EDITOR;
+ char *str = nk_str_get(&handle->str);
+ int len = nk_str_len(&handle->str);
+
+ if(len > 0) //FIXME
+ {
+ const float content_h = nk_window_get_height(ctx) - 2*window_padding.y - 2*group_padding.y;
+ nk_layout_row_dynamic(ctx, content_h, 1);
+ nk_edit_focus(ctx, flags);
+ const nk_flags mode = nk_edit_string(ctx, flags, str, &len, len, nk_filter_default);
+ (void)mode;
+ }
+
+ nk_group_end(ctx);
+ }
+ }
+ nk_end(ctx);
+}
diff --git a/cmake/arm-linux-gnueabihf.cmake b/cmake/arm-linux-gnueabihf.cmake
new file mode 100644
index 0000000..f6d0b2a
--- /dev/null
+++ b/cmake/arm-linux-gnueabihf.cmake
@@ -0,0 +1,22 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Linux)
+set(CMAKE_SYSTEM_PROCESSOR "armv7h")
+set(TOOLCHAIN "arm-linux-gnueabihf")
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER "${TOOLCHAIN}-gcc")
+set(CMAKE_CXX_COMPILER "${TOOLCHAIN}-g++")
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH "usr/${TOOLCHAIN}")
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(STATIC_SRATOM "/opt/${TOOLCHAIN}/lib/libsratom-0.a")
+set(STATIC_SERD "/opt/${TOOLCHAIN}/lib/libserd-0.a")
+set(STATIC_SORD "/opt/${TOOLCHAIN}/lib/libsord-0.a")
diff --git a/cmake/i686-linux-gnu.cmake b/cmake/i686-linux-gnu.cmake
new file mode 100644
index 0000000..f00ae3e
--- /dev/null
+++ b/cmake/i686-linux-gnu.cmake
@@ -0,0 +1,11 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Linux)
+set(CMAKE_SYSTEM_PROCESSOR "i686")
+set(TOOLCHAIN "i686-linux-gnu")
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -m32" CACHE STRING "c++ flags")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -m32" CACHE STRING "c flags")
+
+set(STATIC_SRATOM "/opt/${TOOLCHAIN}/lib/libsratom-0.a")
+set(STATIC_SERD "/opt/${TOOLCHAIN}/lib/libserd-0.a")
+set(STATIC_SORD "/opt/${TOOLCHAIN}/lib/libsord-0.a")
diff --git a/cmake/i686-w64-mingw32.cmake b/cmake/i686-w64-mingw32.cmake
new file mode 100644
index 0000000..3564cad
--- /dev/null
+++ b/cmake/i686-w64-mingw32.cmake
@@ -0,0 +1,25 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+set(CMAKE_SYSTEM_PROCESSOR "i686")
+set(TOOLCHAIN "i686-w64-mingw32")
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER "${TOOLCHAIN}-gcc")
+set(CMAKE_CXX_COMPILER "${TOOLCHAIN}-g++")
+set(CMAKE_RC_COMPILER "${TOOLCHAIN}-windres")
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH "/usr/${TOOLCHAIN}")
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(STATIC_SRATOM "/opt/${TOOLCHAIN}/lib/libsratom-0.a")
+set(STATIC_SERD "/opt/${TOOLCHAIN}/lib/libserd-0.a")
+set(STATIC_SORD "/opt/${TOOLCHAIN}/lib/libsord-0.a")
+
+set(WINE wine32)
diff --git a/cmake/universal-apple-darwin.cmake b/cmake/universal-apple-darwin.cmake
new file mode 100644
index 0000000..127e80f
--- /dev/null
+++ b/cmake/universal-apple-darwin.cmake
@@ -0,0 +1,24 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Darwin)
+set(CMAKE_SYSTEM_PROCESSOR "x86_64")
+set(TOOLCHAIN "universal-apple-darwin")
+
+set(CMAKE_OSX_ARCHITECTURES "x86_64;i386")
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER "/usr/${TOOLCHAIN}/bin/x86_64-apple-darwin15-clang")
+set(CMAKE_CXX_COMPILER "/usr/${TOOLCHAIN}/bin/x86_64-apple-darwin15-clang++")
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH "/usr/${TOOLCHAIN}/SDK/MacOSX10.11.sdk")
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(STATIC_SRATOM "/opt/${TOOLCHAIN}/lib/libsratom-0.a")
+set(STATIC_SERD "/opt/${TOOLCHAIN}/lib/libserd-0.a")
+set(STATIC_SORD "/opt/${TOOLCHAIN}/lib/libsord-0.a")
diff --git a/cmake/x86_64-linux-gnu.cmake b/cmake/x86_64-linux-gnu.cmake
new file mode 100644
index 0000000..730a594
--- /dev/null
+++ b/cmake/x86_64-linux-gnu.cmake
@@ -0,0 +1,8 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Linux)
+set(CMAKE_SYSTEM_PROCESSOR "x86_64")
+set(TOOLCHAIN "x86_64-linux-gnu")
+
+set(STATIC_SRATOM "/opt/${TOOLCHAIN}/lib/libsratom-0.a")
+set(STATIC_SERD "/opt/${TOOLCHAIN}/lib/libserd-0.a")
+set(STATIC_SORD "/opt/${TOOLCHAIN}/lib/libsord-0.a")
diff --git a/cmake/x86_64-w64-mingw32.cmake b/cmake/x86_64-w64-mingw32.cmake
new file mode 100644
index 0000000..9e90894
--- /dev/null
+++ b/cmake/x86_64-w64-mingw32.cmake
@@ -0,0 +1,25 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+set(CMAKE_SYSTEM_PROCESSOR "x86_64")
+set(TOOLCHAIN "x86_64-w64-mingw32")
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER "${TOOLCHAIN}-gcc")
+set(CMAKE_CXX_COMPILER "${TOOLCHAIN}-g++")
+set(CMAKE_RC_COMPILER "${TOOLCHAIN}-windres")
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH "/usr/${TOOLCHAIN}")
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+
+set(STATIC_SRATOM "/opt/${TOOLCHAIN}/lib/libsratom-0.a")
+set(STATIC_SERD "/opt/${TOOLCHAIN}/lib/libserd-0.a")
+set(STATIC_SORD "/opt/${TOOLCHAIN}/lib/libsord-0.a")
+
+set(WINE wine64)
diff --git a/encoder.l b/encoder.l
new file mode 100644
index 0000000..d076f67
--- /dev/null
+++ b/encoder.l
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+%{
+#include <stdio.h>
+#include <string.h>
+
+#include <common.h>
+
+typedef enum _markup_type_t markup_type_t;
+typedef struct _markup_item_t markup_item_t;
+
+enum _markup_type_t {
+ MARKUP_CODE,
+ MARKUP_PREFIX,
+ MARKUP_SUBJECT,
+ MARKUP_PREDICATE,
+ MARKUP_NUMBER,
+ MARKUP_STRING,
+ MARKUP_URI
+};
+
+struct _markup_item_t {
+ const char *begin;
+ const char *end;
+};
+
+static const markup_item_t markup_items [] = {
+ [MARKUP_CODE] = {"font=Mono style=Plain color=#ffffff", "font"},
+ [MARKUP_PREFIX] = {"color=#cc00cc", "color"},
+ [MARKUP_SUBJECT] = {"color=#00cccc", "color"},
+ [MARKUP_PREDICATE] = {"color=#00cc00", "color"},
+ [MARKUP_NUMBER] = {"color=#0000cc", "color"},
+ [MARKUP_STRING] = {"color=#cc0000", "color"},
+ [MARKUP_URI] = {"color=#cccc00", "color"}
+};
+
+static void
+_add_plain(const char *content)
+{
+ encoder->append(content, encoder->data);
+}
+
+static void
+_add_singleton(const char *key)
+{
+ char buf [64];
+ sprintf(buf, "<%s/>", key);
+ encoder->append(buf, encoder->data);
+}
+
+static void
+_add_markup_begin(markup_type_t type)
+{
+ char buf [64];
+ sprintf(buf, "<%s>", markup_items[type].begin);
+ encoder->append(buf, encoder->data);
+}
+
+static void
+_add_markup_end(markup_type_t type)
+{
+ char buf [64];
+ sprintf(buf, "</%s>", markup_items[type].end);
+ encoder->append(buf, encoder->data);
+}
+
+static void
+_add_markup(markup_type_t type, const char *content)
+{
+ char buf [64];
+ sprintf(buf, "<%s>", markup_items[type].begin);
+ encoder->append(buf, encoder->data);
+ encoder->append(content, encoder->data);
+ sprintf(buf, "</%s>", markup_items[type].end);
+ encoder->append(buf, encoder->data);
+}
+
+enum {
+ TK_NONE,
+ TK_PREFIX,
+ TK_SUBJECT,
+ TK_PREDICATE,
+ TK_NUMBER,
+ TK_URI_IN,
+ TK_URI_OUT,
+ TK_URI_ERR,
+ TK_STRING_IN,
+ TK_STRING_OUT,
+ TK_STRING_ERR,
+ TK_LONG_STRING_IN,
+ TK_LONG_STRING_OUT,
+ TK_WHITESPACE,
+ TK_RAW,
+ TK_TAB,
+ TK_NEWLINE,
+ TK_LT,
+ TK_GT,
+ TK_AMP,
+ TK_NAME,
+ TK_BADCHAR
+};
+
+%}
+
+%option reentrant noyywrap
+
+w [ \v\a]+
+name [_a-zA-Z@][_a-zA-Z0-9\.]*
+n [0-9]+
+exp [Ee][+-]?{n}
+number ({n}|{n}[.]{n}){exp}?
+eol [\n\r]
+
+%x XSTRING
+%x XLONG_STRING
+%x XURI
+
+%%
+
+{w} return TK_WHITESPACE;
+"\t" return TK_TAB;
+{eol} return TK_NEWLINE;
+"<" BEGIN(XURI); return TK_URI_IN;
+\"\"\" BEGIN(XLONG_STRING); return TK_LONG_STRING_IN;
+\" BEGIN(XSTRING); return TK_STRING_IN;
+{name}: return TK_SUBJECT;
+"@prefix" return TK_PREFIX;
+"a" return TK_PREFIX;
+{name} return TK_PREDICATE;
+{number} return TK_NUMBER;
+. return TK_RAW;
+
+<XURI>
+{
+ ">" BEGIN(0); return TK_URI_OUT;
+ {eol} BEGIN(0); return TK_URI_ERR;
+ . return TK_RAW;
+}
+
+<XLONG_STRING>
+{
+ \\\" return TK_RAW;
+ \"\"\" BEGIN(0); return TK_LONG_STRING_OUT;
+ {w} return TK_WHITESPACE;
+ "\t" return TK_TAB;
+ {eol} return TK_NEWLINE;
+ "<" return TK_LT;
+ ">" return TK_GT;
+ "&" return TK_AMP;
+ . return TK_RAW;
+}
+
+<XSTRING>
+{
+ \\\" return TK_RAW;
+ \" BEGIN(0); return TK_STRING_OUT;
+ {eol} BEGIN(0); return TK_STRING_ERR;
+ {w} return TK_WHITESPACE;
+ "\t" return TK_TAB;
+ {eol} return TK_NEWLINE;
+ "<" return TK_LT;
+ ">" return TK_GT;
+ "&" return TK_AMP;
+ . return TK_RAW;
+}
+
+%%
+
+void
+ttl_to_markup(const char *utf8, FILE *f)
+{
+ yyscan_t scanner;
+ YY_BUFFER_STATE buf;
+
+ enclex_init(&scanner);
+ if(utf8)
+ {
+ buf = enc_scan_string(utf8, scanner);
+ }
+ else if(f)
+ {
+ encset_in(f, scanner);
+ buf = enc_create_buffer(NULL, YY_BUF_SIZE, scanner);
+ }
+ else
+ {
+ enclex_destroy(scanner);
+ return;
+ }
+
+ encoder->begin(encoder->data);
+ _add_markup_begin(MARKUP_CODE);
+
+ for(int tok=enclex(scanner); tok; tok=enclex(scanner))
+ {
+ const char *txt = encget_text(scanner);
+ switch(tok)
+ {
+ case TK_PREFIX:
+ _add_markup(MARKUP_PREFIX, txt);
+ break;
+
+ case TK_NUMBER:
+ _add_markup(MARKUP_NUMBER, txt);
+ break;
+
+ case TK_URI_IN:
+ _add_markup_begin(MARKUP_URI);
+ _add_plain("&lt;");
+ break;
+ case TK_URI_OUT:
+ _add_plain("&gt;");
+ _add_markup_end(MARKUP_URI);
+ break;
+ case TK_URI_ERR:
+ _add_markup_end(MARKUP_URI);
+ _add_singleton("br");
+ break;
+
+ case TK_STRING_IN:
+ _add_markup_begin(MARKUP_STRING);
+ _add_plain("\"");
+ break;
+ case TK_STRING_OUT:
+ _add_plain("\"");
+ _add_markup_end(MARKUP_STRING);
+ break;
+ case TK_STRING_ERR:
+ _add_markup_end(MARKUP_STRING);
+ _add_singleton("br");
+ break;
+
+ case TK_LONG_STRING_IN:
+ _add_markup_begin(MARKUP_STRING);
+ _add_plain("\"\"\"");
+ break;
+ case TK_LONG_STRING_OUT:
+ _add_plain("\"\"\"");
+ _add_markup_end(MARKUP_STRING);
+ break;
+
+ case TK_NEWLINE:
+ _add_singleton("br");
+ break;
+ case TK_LT:
+ _add_plain("&lt;");
+ break;
+ case TK_GT:
+ _add_plain("&gt;");
+ break;
+ case TK_AMP:
+ _add_plain("&amp;");
+ break;
+
+ case TK_BADCHAR:
+ break;
+
+ case TK_TAB:
+ _add_plain(" ");
+ break;
+
+ case TK_SUBJECT:
+ _add_markup(MARKUP_SUBJECT, txt);
+ break;
+ case TK_PREDICATE:
+ _add_markup(MARKUP_PREDICATE, txt);
+ break;
+
+ case TK_NAME:
+ case TK_WHITESPACE:
+ case TK_RAW:
+ default:
+ _add_plain(txt);
+ break;
+ }
+ }
+
+ _add_markup_end(MARKUP_CODE);
+ encoder->end(encoder->data);
+
+ enc_delete_buffer(buf, scanner);
+ enclex_destroy(scanner);
+}
diff --git a/manifest.ttl.in b/manifest.ttl.in
new file mode 100644
index 0000000..d71006b
--- /dev/null
+++ b/manifest.ttl.in
@@ -0,0 +1,62 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .
+
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+# Atom Inspector Plugin
+sherlock:atom_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ;
+ lv2:microVersion @SHERLOCK_MICRO_VERSION@ ;
+ lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui sherlock:atom_inspector_4_nk ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:atom_inspector_4_nk
+ a ui:@SHERLOCK_UI_TYPE@ ;
+ ui:binary <sherlock_nk@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+
+# MIDI Inspector Plugin
+sherlock:midi_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ;
+ lv2:microVersion @SHERLOCK_MICRO_VERSION@ ;
+ lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui sherlock:midi_inspector_4_nk ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:midi_inspector_4_nk
+ a ui:@SHERLOCK_UI_TYPE@ ;
+ ui:binary <sherlock_nk@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
+
+# OSC Inspector Plugin
+sherlock:osc_inspector
+ a lv2:Plugin ;
+ lv2:minorVersion @SHERLOCK_MINOR_VERSION@ ;
+ lv2:microVersion @SHERLOCK_MICRO_VERSION@ ;
+ lv2:binary <sherlock@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ ui:ui sherlock:osc_inspector_4_nk ;
+ rdfs:seeAlso <sherlock.ttl> .
+
+sherlock:osc_inspector_4_nk
+ a ui:@SHERLOCK_UI_TYPE@ ;
+ ui:binary <sherlock_nk@CMAKE_SHARED_MODULE_SUFFIX@> ;
+ rdfs:seeAlso <sherlock_ui.ttl> .
diff --git a/midi_inspector.c b/midi_inspector.c
new file mode 100644
index 0000000..29c50a0
--- /dev/null
+++ b/midi_inspector.c
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sherlock.h>
+
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ const LV2_Atom_Sequence *control_in;
+ LV2_Atom_Sequence *control_out;
+ LV2_Atom_Sequence *notify;
+ LV2_Atom_Forge forge;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+ LV2_URID midi_event;
+
+ int64_t frame;
+
+ PROPS_T(props, MAX_NPROPS);
+ state_t state;
+ state_t stash;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(i=0; features[i]; i++)
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = (LV2_URID_Map *)features[i]->data;
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+
+ handle->midi_event = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ if( !props_register(&handle->props, &stat_overwrite, &handle->state.overwrite, &handle->stash.overwrite)
+ || !props_register(&handle->props, &stat_block, &handle->state.block, &handle->stash.block)
+ || !props_register(&handle->props, &stat_follow, &handle->state.follow, &handle->stash.follow) )
+ {
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->control_out = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ uint32_t capacity;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref;
+
+ // size of input sequence
+ const size_t size = lv2_atom_total_size(&handle->control_in->atom);
+
+ // copy whole input sequence to through port
+ capacity = handle->control_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity);
+ ref = lv2_atom_forge_sequence_head(forge, frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ // copy all events to through port
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, &obj->atom, lv2_atom_total_size(&obj->atom));
+ if(ref)
+ lv2_atom_forge_pad(forge, obj->atom.size);
+
+ if( !props_advance(&handle->props, forge, frames, obj, &ref)
+ && lv2_atom_forge_is_object_type(forge, obj->atom.type)
+ && (obj->body.otype == handle->time_position) )
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - frames;
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, frame);
+ else
+ lv2_atom_sequence_clear(handle->control_out);
+
+ // forge whole sequence as single event
+ capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity);
+
+ bool has_midi = false;
+
+ ref = lv2_atom_forge_sequence_head(forge, &frame[0], 0);
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &frame[1]);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, handle->frame);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_sequence_head(forge, &frame[2], 0);
+
+ // only serialize MIDI events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ if(ev->body.type == handle->midi_event)
+ {
+ has_midi = true;
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[2]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[0]);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ if(!has_midi) // don't send anything
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ handle_t *handle = instance;
+
+ return props_save(&handle->props, &handle->forge, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ handle_t *handle = instance;
+
+ return props_restore(&handle->props, &handle->forge, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+ return NULL;
+}
+
+const LV2_Descriptor midi_inspector = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/midi_inspector_nk.c b/midi_inspector_nk.c
new file mode 100644
index 0000000..2ac4d4f
--- /dev/null
+++ b/midi_inspector_nk.c
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <inttypes.h>
+
+#include <sherlock.h>
+#include <sherlock_nk.h>
+
+typedef struct _midi_msg_t midi_msg_t;
+
+struct _midi_msg_t {
+ uint8_t type;
+ const char *key;
+};
+
+#define COMMANDS_NUM 18
+static const midi_msg_t commands [COMMANDS_NUM] = {
+ { LV2_MIDI_MSG_NOTE_OFF , "NoteOff" },
+ { LV2_MIDI_MSG_NOTE_ON , "NoteOn" },
+ { LV2_MIDI_MSG_NOTE_PRESSURE , "NotePressure" },
+ { LV2_MIDI_MSG_CONTROLLER , "Controller" },
+ { LV2_MIDI_MSG_PGM_CHANGE , "ProgramChange" },
+ { LV2_MIDI_MSG_CHANNEL_PRESSURE , "ChannelPressure" },
+ { LV2_MIDI_MSG_BENDER , "Bender" },
+ { LV2_MIDI_MSG_SYSTEM_EXCLUSIVE , "SystemExclusive" },
+ { LV2_MIDI_MSG_MTC_QUARTER , "QuarterFrame" },
+ { LV2_MIDI_MSG_SONG_POS , "SongPosition" },
+ { LV2_MIDI_MSG_SONG_SELECT , "SongSelect" },
+ { LV2_MIDI_MSG_TUNE_REQUEST , "TuneRequest" },
+ { LV2_MIDI_MSG_CLOCK , "Clock" },
+ { LV2_MIDI_MSG_START , "Start" },
+ { LV2_MIDI_MSG_CONTINUE , "Continue" },
+ { LV2_MIDI_MSG_STOP , "Stop" },
+ { LV2_MIDI_MSG_ACTIVE_SENSE , "ActiveSense" },
+ { LV2_MIDI_MSG_RESET , "Reset" },
+};
+
+#define CONTROLLERS_NUM 72
+static const midi_msg_t controllers [CONTROLLERS_NUM] = {
+ { LV2_MIDI_CTL_MSB_BANK , "BankSelection_MSB" },
+ { LV2_MIDI_CTL_MSB_MODWHEEL , "Modulation_MSB" },
+ { LV2_MIDI_CTL_MSB_BREATH , "Breath_MSB" },
+ { LV2_MIDI_CTL_MSB_FOOT , "Foot_MSB" },
+ { LV2_MIDI_CTL_MSB_PORTAMENTO_TIME , "PortamentoTime_MSB" },
+ { LV2_MIDI_CTL_MSB_DATA_ENTRY , "DataEntry_MSB" },
+ { LV2_MIDI_CTL_MSB_MAIN_VOLUME , "MainVolume_MSB" },
+ { LV2_MIDI_CTL_MSB_BALANCE , "Balance_MSB" },
+ { LV2_MIDI_CTL_MSB_PAN , "Panpot_MSB" },
+ { LV2_MIDI_CTL_MSB_EXPRESSION , "Expression_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT1 , "Effect1_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT2 , "Effect2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE1 , "GeneralPurpose1_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE2 , "GeneralPurpose2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE3 , "GeneralPurpose3_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE4 , "GeneralPurpose4_MSB" },
+
+ { LV2_MIDI_CTL_LSB_BANK , "BankSelection_LSB" },
+ { LV2_MIDI_CTL_LSB_MODWHEEL , "Modulation_LSB" },
+ { LV2_MIDI_CTL_LSB_BREATH , "Breath_LSB" },
+ { LV2_MIDI_CTL_LSB_FOOT , "Foot_LSB" },
+ { LV2_MIDI_CTL_LSB_PORTAMENTO_TIME , "PortamentoTime_LSB" },
+ { LV2_MIDI_CTL_LSB_DATA_ENTRY , "DataEntry_LSB" },
+ { LV2_MIDI_CTL_LSB_MAIN_VOLUME , "MainVolume_LSB" },
+ { LV2_MIDI_CTL_LSB_BALANCE , "Balance_LSB" },
+ { LV2_MIDI_CTL_LSB_PAN , "Panpot_LSB" },
+ { LV2_MIDI_CTL_LSB_EXPRESSION , "Expression_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT1 , "Effect1_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT2 , "Effect2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE1 , "GeneralPurpose1_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE2 , "GeneralPurpose2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE3 , "GeneralPurpose3_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE4 , "GeneralPurpose4_LSB" },
+
+ { LV2_MIDI_CTL_SUSTAIN , "SustainPedal" },
+ { LV2_MIDI_CTL_PORTAMENTO , "Portamento" },
+ { LV2_MIDI_CTL_SOSTENUTO , "Sostenuto" },
+ { LV2_MIDI_CTL_SOFT_PEDAL , "SoftPedal" },
+ { LV2_MIDI_CTL_LEGATO_FOOTSWITCH , "LegatoFootSwitch" },
+ { LV2_MIDI_CTL_HOLD2 , "Hold2" },
+
+ { LV2_MIDI_CTL_SC1_SOUND_VARIATION , "SC1_SoundVariation" },
+ { LV2_MIDI_CTL_SC2_TIMBRE , "SC2_Timbre" },
+ { LV2_MIDI_CTL_SC3_RELEASE_TIME , "SC3_ReleaseTime" },
+ { LV2_MIDI_CTL_SC4_ATTACK_TIME , "SC4_AttackTime" },
+ { LV2_MIDI_CTL_SC5_BRIGHTNESS , "SC5_Brightness" },
+ { LV2_MIDI_CTL_SC6 , "SC6" },
+ { LV2_MIDI_CTL_SC7 , "SC7" },
+ { LV2_MIDI_CTL_SC8 , "SC8" },
+ { LV2_MIDI_CTL_SC9 , "SC9" },
+ { LV2_MIDI_CTL_SC10 , "SC10" },
+
+ { LV2_MIDI_CTL_GENERAL_PURPOSE5 , "GeneralPurpose5" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE6 , "GeneralPurpose6" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE7 , "GeneralPurpose7" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE8 , "GeneralPurpose8" },
+ { LV2_MIDI_CTL_PORTAMENTO_CONTROL , "PortamentoControl" },
+
+ { LV2_MIDI_CTL_E1_REVERB_DEPTH , "E1_ReverbDepth" },
+ { LV2_MIDI_CTL_E2_TREMOLO_DEPTH , "E2_TremoloDepth" },
+ { LV2_MIDI_CTL_E3_CHORUS_DEPTH , "E3_ChorusDepth" },
+ { LV2_MIDI_CTL_E4_DETUNE_DEPTH , "E4_DetuneDepth" },
+ { LV2_MIDI_CTL_E5_PHASER_DEPTH , "E5_PhaserDepth" },
+
+ { LV2_MIDI_CTL_DATA_INCREMENT , "DataIncrement" },
+ { LV2_MIDI_CTL_DATA_DECREMENT , "DataDecrement" },
+
+ { LV2_MIDI_CTL_NRPN_LSB , "NRPN_LSB" },
+ { LV2_MIDI_CTL_NRPN_MSB , "NRPN_MSB" },
+
+ { LV2_MIDI_CTL_RPN_LSB , "RPN_LSB" },
+ { LV2_MIDI_CTL_RPN_MSB , "RPN_MSB" },
+
+ { LV2_MIDI_CTL_ALL_SOUNDS_OFF , "AllSoundsOff" },
+ { LV2_MIDI_CTL_RESET_CONTROLLERS , "ResetControllers" },
+ { LV2_MIDI_CTL_LOCAL_CONTROL_SWITCH , "LocalControlSwitch" },
+ { LV2_MIDI_CTL_ALL_NOTES_OFF , "AllNotesOff" },
+ { LV2_MIDI_CTL_OMNI_OFF , "OmniOff" },
+ { LV2_MIDI_CTL_OMNI_ON , "OmniOn" },
+ { LV2_MIDI_CTL_MONO1 , "Mono1" },
+ { LV2_MIDI_CTL_MONO2 , "Mono2" },
+};
+
+#define TIMECODES_NUM 8
+static const midi_msg_t timecodes [TIMECODES_NUM] = {
+ { 0 , "FrameNumber_LSB" },
+ { 1 , "FrameNumber_MSB" },
+ { 2 , "Second_LSB" },
+ { 3 , "Second_MSB" },
+ { 4 , "Minute_LSB" },
+ { 5 , "Minute_MSB" },
+ { 6 , "Hour_LSB" },
+ { 7 , "RateAndHour_MSB" },
+};
+
+static int
+_cmp_search(const void *itm1, const void *itm2)
+{
+ const midi_msg_t *msg1 = itm1;
+ const midi_msg_t *msg2 = itm2;
+
+ if(msg1->type < msg2->type)
+ return -1;
+ else if(msg1->type > msg2->type)
+ return 1;
+
+ return 0;
+}
+
+static inline const midi_msg_t *
+_search_command(uint8_t type)
+{
+ return bsearch(&type, commands, COMMANDS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static inline const midi_msg_t *
+_search_controller(uint8_t type)
+{
+ return bsearch(&type, controllers, CONTROLLERS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static inline const midi_msg_t *
+_search_timecode(uint8_t type)
+{
+ return bsearch(&type, timecodes, TIMECODES_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static const char *keys [12] = {
+ "C", "C#",
+ "D", "D#",
+ "E",
+ "F", "F#",
+ "G", "G#",
+ "A", "A#",
+ "B"
+};
+
+static inline const char *
+_note(uint8_t val, int8_t *octave)
+{
+ *octave = val / 12 - 1;
+
+ return keys[val % 12];
+}
+
+static inline void
+_shadow(struct nk_context *ctx, bool *shadow)
+{
+ if(*shadow)
+ {
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 10;
+ b.w += 8*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
+ }
+
+ *shadow = !*shadow;
+}
+
+void
+_midi_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ const float widget_h = handle->dy;
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 window_padding = style->window.padding;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+
+ if(nk_begin(ctx, "Window", wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ nk_window_set_bounds(ctx, wbounds);
+ struct nk_panel *panel = nk_window_get_panel(ctx);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h;
+ nk_layout_row_dynamic(ctx, body_h, 1);
+ nk_flags flags = NK_WINDOW_BORDER;
+ if(handle->state.follow)
+ flags |= NK_WINDOW_NO_SCROLLBAR;
+ struct nk_list_view lview;
+ if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
+ {
+ if(handle->state.follow)
+ {
+ lview.end = NK_MAX(handle->n_item, 0);
+ lview.begin = NK_MAX(lview.end - lview.count, 0);
+ }
+ handle->shadow = lview.begin % 2 == 0;
+ for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
+ {
+ item_t *itm = handle->items[l];
+
+ switch(itm->type)
+ {
+ case ITEM_TYPE_NONE:
+ {
+ // skip, was sysex payload
+ } break;
+ case ITEM_TYPE_FRAME:
+ {
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 3;
+ b.w += 4*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
+ nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
+ } break;
+
+ case ITEM_TYPE_EVENT:
+ {
+ LV2_Atom_Event *ev = &itm->event.ev;
+ const LV2_Atom *body = &ev->body;
+ const int64_t frames = ev->time.frames;
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(body);
+ const uint8_t cmd = (msg[0] & 0xf0) == 0xf0
+ ? msg[0]
+ : msg[0] & 0xf0;
+
+ const midi_msg_t *command_msg = _search_command(cmd);
+ const char *command_str = command_msg
+ ? command_msg->key
+ : "Unknown";
+
+ char tmp [16];
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7);
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _shadow(ctx, &handle->shadow);
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
+
+ nk_layout_row_push(ctx, 0.2);
+ const unsigned rem = body->size;
+ const unsigned to = rem >= 4 ? 4 : rem % 4;
+ for(unsigned i=0, ptr=0; i<to; i++, ptr+=3)
+ sprintf(&tmp[ptr], "%02"PRIX8" ", msg[i]);
+ tmp[to*3 - 1] = '\0';
+ nk_label_colored(ctx, tmp, NK_TEXT_LEFT, white);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_label_colored(ctx, command_str, NK_TEXT_LEFT, magenta);
+
+ switch(cmd)
+ {
+ case LV2_MIDI_MSG_NOTE_OFF:
+ // fall-through
+ case LV2_MIDI_MSG_NOTE_ON:
+ // fall-through
+ case LV2_MIDI_MSG_NOTE_PRESSURE:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "Ch:%02"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ nk_layout_row_push(ctx, 0.2);
+ int8_t octave;
+ const char *key = _note(msg[1], &octave);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%s%+"PRIi8, key, octave);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIu8, msg[2]);
+ } break;
+ case LV2_MIDI_MSG_CONTROLLER:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "Ch:%02"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ const midi_msg_t *controller_msg = _search_controller(msg[1]);
+ const char *controller_str = controller_msg
+ ? controller_msg->key
+ : "Unknown";
+ nk_layout_row_push(ctx, 0.2);
+ nk_label_colored(ctx, controller_str, NK_TEXT_RIGHT, white);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIu8, msg[2]);
+ } break;
+ case LV2_MIDI_MSG_PGM_CHANGE:
+ // fall-through
+ case LV2_MIDI_MSG_CHANNEL_PRESSURE:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "Ch:%02"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIu8, msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_BENDER:
+ {
+ const int16_t bender = (((int16_t)msg[2] << 7) | msg[1]) - 0x2000;
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "Ch:%02"PRIu8,
+ (msg[0] & 0x0f) + 1);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIi16, bender);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_MTC_QUARTER:
+ {
+ const uint8_t msg_type = msg[1] >> 4;
+ const uint8_t msg_val = msg[1] & 0xf;
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ const midi_msg_t *timecode_msg = _search_timecode(msg_type);
+ const char *timecode_str = timecode_msg
+ ? timecode_msg->key
+ : "Unknown";
+ nk_layout_row_push(ctx, 0.2);
+ nk_label_colored(ctx, timecode_str, NK_TEXT_RIGHT, white);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIu8, msg_val);
+ } break;
+ case LV2_MIDI_MSG_SONG_POS:
+ {
+ const int16_t song_pos= (((int16_t)msg[2] << 7) | msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIu16, song_pos);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_SONG_SELECT:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, white, "%"PRIu8, msg[1]);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE:
+ // fall-throuh
+ case LV2_MIDI_MSG_TUNE_REQUEST:
+ // fall-throuh
+ case LV2_MIDI_MSG_CLOCK:
+ // fall-throuh
+ case LV2_MIDI_MSG_START:
+ // fall-throuh
+ case LV2_MIDI_MSG_CONTINUE:
+ // fall-throuh
+ case LV2_MIDI_MSG_STOP:
+ // fall-throuh
+ case LV2_MIDI_MSG_ACTIVE_SENSE:
+ // fall-throuh
+ case LV2_MIDI_MSG_RESET:
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ } break;
+ }
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size);
+ }
+ nk_layout_row_end(ctx);
+
+ for(unsigned j=4; j<body->size; j+=4)
+ {
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7);
+ {
+ nk_layout_row_push(ctx, 0.1);
+ _shadow(ctx, &handle->shadow);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ const unsigned rem = body->size - j;
+ const unsigned to = rem >= 4 ? 4 : rem % 4;
+ for(unsigned i=0, ptr=0; i<to; i++, ptr+=3)
+ sprintf(&tmp[ptr], "%02"PRIX8" ", msg[j+i]);
+ tmp[to*3 - 1] = '\0';
+ nk_label_colored(ctx, tmp, NK_TEXT_LEFT, white);
+
+ nk_layout_row_push(ctx, 0.2);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.2);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ _empty(ctx);
+ }
+ }
+ } break;
+ }
+ }
+
+ nk_list_view_end(&lview);
+ }
+
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ if(nk_checkbox_label(ctx, "overwrite", &handle->state.overwrite))
+ _toggle(handle, handle->urid.overwrite, handle->state.overwrite, true);
+ if(nk_checkbox_label(ctx, "block", &handle->state.block))
+ _toggle(handle, handle->urid.block, handle->state.block, true);
+ if(nk_checkbox_label(ctx, "follow", &handle->state.follow))
+ _toggle(handle, handle->urid.follow, handle->state.follow, true);
+ }
+
+ const bool max_reached = handle->n_item >= MAX_LINES;
+ nk_layout_row_dynamic(ctx, widget_h, 2);
+ if(nk_button_symbol_label(ctx,
+ max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
+ "clear", NK_TEXT_LEFT))
+ {
+ _clear(handle);
+ }
+ nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
+ }
+ nk_end(ctx);
+}
diff --git a/nk_pugl/COPYING b/nk_pugl/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/nk_pugl/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/nk_pugl/nk_pugl.h b/nk_pugl/nk_pugl.h
new file mode 100644
index 0000000..6b06828
--- /dev/null
+++ b/nk_pugl/nk_pugl.h
@@ -0,0 +1,953 @@
+/*
+ * Copyright (c) 2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _NK_PUGL_H
+#define _NK_PUGL_H
+
+#include <stdatomic.h>
+
+#ifdef __cplusplus
+extern C {
+#endif
+
+#include "pugl/pugl.h"
+#include "pugl/gl.h"
+
+#if defined(_WIN32)
+# include <windows.h> // Broken Windows GL headers require this
+# include "GL/wglext.h"
+# include "GL/glext.h"
+#endif
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+# include "GL/glx.h"
+# include "GL/glext.h"
+#endif
+
+#define NK_ZERO_COMMAND_MEMORY
+#define NK_INCLUDE_FIXED_TYPES
+#define NK_INCLUDE_DEFAULT_ALLOCATOR
+#define NK_INCLUDE_STANDARD_IO
+#define NK_INCLUDE_STANDARD_VARARGS
+#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+#define NK_INCLUDE_FONT_BAKING
+#define NK_INCLUDE_DEFAULT_FONT
+#define NK_SIN sinf
+#define NK_COS cosf
+#define NK_SQRT sqrtf
+
+#include "nuklear/nuklear.h"
+#include "nuklear/example/stb_image.h"
+
+typedef struct _nk_pugl_config_t nk_pugl_config_t;
+typedef struct _nk_pugl_window_t nk_pugl_window_t;
+typedef void (*nkglGenerateMipmap)(GLenum target);
+typedef void (*nk_pugl_expose_t)(struct nk_context *ctx,
+ struct nk_rect wbounds, void *data);
+
+struct _nk_pugl_config_t {
+ unsigned width;
+ unsigned height;
+
+ bool resizable;
+ bool ignore;
+ const char *class;
+ const char *title;
+
+ struct {
+ const char *face;
+ int size;
+ } font;
+
+ intptr_t parent;
+
+ void *data;
+ nk_pugl_expose_t expose;
+};
+
+struct _nk_pugl_window_t {
+ nk_pugl_config_t cfg;
+
+ PuglView *view;
+ int quit;
+ bool input_active;
+
+ struct nk_buffer cmds;
+ struct nk_draw_null_texture null;
+ struct nk_context ctx;
+ struct nk_font_atlas atlas;
+ struct nk_convert_config conv;
+ struct {
+ void *buffer;
+ size_t size;
+ } last;
+
+ GLuint font_tex;
+ nkglGenerateMipmap glGenerateMipmap;
+
+ intptr_t widget;
+#if !defined(__APPLE__) && !defined(_WIN32)
+ atomic_flag async;
+ Display *disp;
+#endif
+};
+
+static inline intptr_t
+nk_pugl_init(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_show(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_hide(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_shutdown(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_wait_for_event(nk_pugl_window_t *win);
+
+static inline int
+nk_pugl_process_events(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_post_redisplay(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_async_redisplay(nk_pugl_window_t *win);
+
+static inline void
+nk_pugl_quit(nk_pugl_window_t *win);
+
+static struct nk_image
+nk_pugl_icon_load(nk_pugl_window_t *win, const char *filename);
+
+static void
+nk_pugl_icon_unload(nk_pugl_window_t *win, struct nk_image img);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _NK_PUGL_H
+
+#ifdef NK_PUGL_IMPLEMENTATION
+
+#ifdef __cplusplus
+extern C {
+#endif
+
+#define NK_ZERO_COMMAND_MEMORY
+#define NK_INCLUDE_FIXED_TYPES
+#define NK_INCLUDE_DEFAULT_ALLOCATOR
+#define NK_INCLUDE_STANDARD_IO
+#define NK_INCLUDE_STANDARD_VARARGS
+#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
+#define NK_INCLUDE_FONT_BAKING
+#define NK_INCLUDE_DEFAULT_FONT
+#define NK_SIN sinf
+#define NK_COS cosf
+#define NK_SQRT sqrtf
+
+# define NK_IMPLEMENTATION
+# include "nuklear/nuklear.h"
+
+# define STB_IMAGE_IMPLEMENTATION
+# include "nuklear/example/stb_image.h"
+
+typedef struct _nk_pugl_vertex_t nk_pugl_vertex_t;
+
+struct _nk_pugl_vertex_t {
+ float position [2];
+ float uv [2];
+ nk_byte col [4];
+};
+
+static const struct nk_draw_vertex_layout_element vertex_layout [] = {
+ {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(nk_pugl_vertex_t, position)},
+ {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(nk_pugl_vertex_t, uv)},
+ {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(nk_pugl_vertex_t, col)},
+ {NK_VERTEX_LAYOUT_END}
+};
+
+#if defined(__APPLE__)
+# define GL_EXT(name) name
+#endif
+
+#if defined(_WIN32)
+static void *
+_nk_pugl_gl_ext(const char *name)
+{
+ void *p = wglGetProcAddress(name);
+
+ if( (p == 0) || (p == (void *)-1)
+ || (p == (void *)0x1) || (p == (void *)0x2) || (p == (void *)0x3) )
+ {
+ HMODULE module = LoadLibraryA("opengl32.dll");
+ p = (void *)GetProcAddress(module, name);
+ }
+
+ if(!p)
+ fprintf(stderr, "[GL]: failed to load extension: %s", name);
+
+ return p;
+}
+# define GL_EXT(name) (nk##name)_nk_pugl_gl_ext(#name)
+#endif
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+static void *
+_nk_pugl_gl_ext(const char *name)
+{
+ void *p = glXGetProcAddress((const GLubyte*)name);
+
+ if(!p)
+ fprintf(stderr, "[GL]: failed to load extension: %s", name);
+
+ return p;
+}
+# define GL_EXT(name) (nk##name)_nk_pugl_gl_ext(#name)
+#endif
+
+static inline void
+_nk_pugl_device_upload_atlas(nk_pugl_window_t *win, const void *image,
+ int width, int height)
+{
+ glGenTextures(1, &win->font_tex);
+ glBindTexture(GL_TEXTURE_2D, win->font_tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, image);
+}
+
+static inline void
+_nk_pugl_render_gl2_push(unsigned width, unsigned height)
+{
+ glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT);
+
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_SCISSOR_TEST);
+ glEnable(GL_BLEND);
+ glEnable(GL_TEXTURE_2D);
+
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glViewport(0, 0, width, height);
+
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0.f, width, height, 0.f, -1.f, 1.f);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+}
+
+static inline void
+_nk_pugl_render_gl2_pop(void)
+{
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_BLEND);
+ glDisable(GL_TEXTURE_2D);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ glMatrixMode(GL_MODELVIEW);
+ glPopMatrix();
+
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+
+ glPopAttrib();
+}
+
+static inline void
+_nk_pugl_render_gl2(nk_pugl_window_t *win)
+{
+ nk_pugl_config_t *cfg = &win->cfg;
+
+ // compare current command buffer with last one to defer any changes
+ bool has_changes = false;
+ const size_t size = win->ctx.memory.allocated;
+ const void *commands = nk_buffer_memory_const(&win->ctx.memory);
+
+ if( (size != win->last.size) || memcmp(commands, win->last.buffer, size))
+ {
+ // swap last buffer with current one for next comparison
+ win->last.buffer = realloc(win->last.buffer, size);
+ if(win->last.buffer)
+ {
+ win->last.size = size;
+ memcpy(win->last.buffer, commands, size);
+ }
+ else
+ {
+ win->last.size = 0;
+ }
+ has_changes = true;
+ }
+
+ // only render if there were actually any changes
+ if(has_changes)
+ {
+ // convert shapes into vertexes
+ struct nk_buffer vbuf, ebuf;
+ nk_buffer_init_default(&vbuf);
+ nk_buffer_init_default(&ebuf);
+ nk_convert(&win->ctx, &win->cmds, &vbuf, &ebuf, &win->conv);
+
+ _nk_pugl_render_gl2_push(cfg->width, cfg->height);
+
+ // setup vertex buffer pointers
+ const GLsizei vs = sizeof(nk_pugl_vertex_t);
+ const size_t vp = offsetof(nk_pugl_vertex_t, position);
+ const size_t vt = offsetof(nk_pugl_vertex_t, uv);
+ const size_t vc = offsetof(nk_pugl_vertex_t, col);
+ const nk_byte *vertices = nk_buffer_memory_const(&vbuf);
+ glVertexPointer(2, GL_FLOAT, vs, &vertices[vp]);
+ glTexCoordPointer(2, GL_FLOAT, vs, &vertices[vt]);
+ glColorPointer(4, GL_UNSIGNED_BYTE, vs, &vertices[vc]);
+
+ // iterate over and execute each draw command
+ const nk_draw_index *offset = nk_buffer_memory_const(&ebuf);
+ const struct nk_draw_command *cmd;
+ nk_draw_foreach(cmd, &win->ctx, &win->cmds)
+ {
+ if(!cmd->elem_count)
+ continue;
+
+ glBindTexture(GL_TEXTURE_2D, cmd->texture.id);
+ glScissor(
+ cmd->clip_rect.x,
+ cfg->height - (cmd->clip_rect.y + cmd->clip_rect.h),
+ cmd->clip_rect.w,
+ cmd->clip_rect.h);
+ glDrawElements(GL_TRIANGLES, cmd->elem_count, GL_UNSIGNED_SHORT, offset);
+
+ offset += cmd->elem_count;
+ }
+
+ _nk_pugl_render_gl2_pop();
+
+ nk_buffer_free(&vbuf);
+ nk_buffer_free(&ebuf);
+ }
+
+ nk_clear(&win->ctx);
+}
+
+static inline void
+_nk_pugl_font_stash_begin(nk_pugl_window_t *win)
+{
+ struct nk_font_atlas *atlas = &win->atlas;
+
+ nk_font_atlas_init_default(atlas);
+ nk_font_atlas_begin(atlas);
+}
+
+static inline void
+_nk_pugl_font_stash_end(nk_pugl_window_t *win)
+{
+ struct nk_font_atlas *atlas = &win->atlas;
+ int w = 0;
+ int h = 0;
+
+ const void *image = nk_font_atlas_bake(atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
+ _nk_pugl_device_upload_atlas(win, image, w, h);
+ nk_font_atlas_end(atlas, nk_handle_id(win->font_tex), &win->null);
+
+ if(atlas->default_font)
+ nk_style_set_font(&win->ctx, &atlas->default_font->handle);
+}
+
+static void
+_nk_pugl_special_key(struct nk_context *ctx, const PuglEventKey *ev, int down)
+{
+ const bool control = ev->state & PUGL_MOD_CTRL;
+
+ switch(ev->special)
+ {
+ case PUGL_KEY_F1:
+ case PUGL_KEY_F2:
+ case PUGL_KEY_F3:
+ case PUGL_KEY_F4:
+ case PUGL_KEY_F5:
+ case PUGL_KEY_F6:
+ case PUGL_KEY_F7:
+ case PUGL_KEY_F8:
+ case PUGL_KEY_F9:
+ case PUGL_KEY_F10:
+ case PUGL_KEY_F11:
+ case PUGL_KEY_F12:
+ {
+ //TODO
+ } break;
+ case PUGL_KEY_LEFT:
+ {
+ if(control)
+ nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
+ else
+ nk_input_key(ctx, NK_KEY_LEFT, down);
+ } break;
+ case PUGL_KEY_RIGHT:
+ {
+ if(control)
+ nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
+ else
+ nk_input_key(ctx, NK_KEY_RIGHT, down);
+ } break;
+ case PUGL_KEY_UP:
+ {
+ nk_input_key(ctx, NK_KEY_UP, down);
+ } break;
+ case PUGL_KEY_DOWN:
+ {
+ nk_input_key(ctx, NK_KEY_DOWN, down);
+ } break;
+ case PUGL_KEY_PAGE_UP:
+ {
+ nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
+ } break;
+ case PUGL_KEY_PAGE_DOWN:
+ {
+ nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
+ } break;
+ case PUGL_KEY_HOME:
+ {
+ nk_input_key(ctx, NK_KEY_TEXT_START, down);
+ nk_input_key(ctx, NK_KEY_SCROLL_START, down);
+ } break;
+ case PUGL_KEY_END:
+ {
+ nk_input_key(ctx, NK_KEY_TEXT_END, down);
+ nk_input_key(ctx, NK_KEY_SCROLL_END, down);
+ } break;
+ case PUGL_KEY_SHIFT:
+ {
+ nk_input_key(ctx, NK_KEY_SHIFT, down);
+ } break;
+ case PUGL_KEY_CTRL:
+ {
+ nk_input_key(ctx, NK_KEY_CTRL, down);
+ } break;
+ case PUGL_KEY_INSERT:
+ {
+ //TODO
+ } break;
+ case PUGL_KEY_ALT:
+ {
+ //TODO
+ } break;
+ case PUGL_KEY_SUPER:
+ {
+ //TODO
+ } break;
+ }
+}
+
+static void
+_nk_pugl_other_key(struct nk_context *ctx, const PuglEventKey *ev, int down)
+{
+ const bool control = ev->state & PUGL_MOD_CTRL;
+
+ if(control)
+ {
+ switch(ev->character + 96) //FIXME why +96?
+ {
+ case 'c':
+ {
+ nk_input_key(ctx, NK_KEY_COPY, down);
+ } break;
+ case 'v':
+ {
+ nk_input_key(ctx, NK_KEY_PASTE, down);
+ } break;
+ case 'x':
+ {
+ nk_input_key(ctx, NK_KEY_CUT, down);
+ } break;
+ case 'z':
+ {
+ nk_input_key(ctx, NK_KEY_TEXT_UNDO, down);
+ } break;
+ case 'r':
+ {
+ nk_input_key(ctx, NK_KEY_TEXT_REDO, down);
+ } break;
+ case 'b':
+ {
+ nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down);
+ } break;
+ case 'e':
+ {
+ nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down);
+ } break;
+ }
+ }
+ else // !control
+ {
+ switch(ev->character)
+ {
+ case '\r':
+ {
+ nk_input_key(ctx, NK_KEY_ENTER, down);
+ } break;
+ case '\t':
+ {
+ nk_input_key(ctx, NK_KEY_TAB, down);
+ } break;
+ case 127: // Delete
+ {
+ nk_input_key(ctx, NK_KEY_DEL, down);
+ } break;
+ case '\b':
+ {
+ nk_input_key(ctx, NK_KEY_BACKSPACE, down);
+ } break;
+
+ default:
+ {
+ if(ev->character == 'i')
+ nk_input_key(ctx, NK_KEY_TEXT_INSERT_MODE, down);
+ else if(ev->character == 'r')
+ nk_input_key(ctx, NK_KEY_TEXT_REPLACE_MODE, down);
+
+ if(down)
+ nk_input_glyph(ctx, (const char *)ev->utf8);
+ } break;
+ }
+ }
+}
+
+static void
+_nk_pugl_key(struct nk_context *ctx, const PuglEventKey *ev, int down)
+{
+ if(ev->special)
+ _nk_pugl_special_key(ctx, ev, down);
+ else if(ev->character && !ev->filter)
+ _nk_pugl_other_key(ctx, ev, down);
+}
+
+static inline void
+_nk_pugl_expose(PuglView *view)
+{
+ nk_pugl_window_t *win = puglGetHandle(view);
+ nk_pugl_config_t *cfg = &win->cfg;
+ struct nk_context *ctx = &win->ctx;
+
+ const struct nk_rect wbounds = nk_rect(0, 0, cfg->width, cfg->height);
+
+ if(nk_begin(ctx, "__bg__", wbounds, 0))
+ {
+ const struct nk_rect obounds = nk_window_get_bounds(ctx);
+
+ if( (obounds.x != wbounds.x) || (obounds.y != wbounds.y)
+ || (obounds.w != wbounds.w) || (obounds.h != wbounds.h) )
+ {
+ // size has changed
+ nk_window_set_bounds(ctx, wbounds);
+ puglPostRedisplay(view);
+ }
+
+ // clears window with widget background color
+ }
+ nk_end(ctx);
+
+ if(cfg->expose)
+ cfg->expose(ctx, wbounds, cfg->data);
+
+ _nk_pugl_render_gl2(win);
+}
+
+static void
+_nk_pugl_dummy_func(PuglView *view, const PuglEvent *e)
+{
+ // do nothing
+}
+
+static void
+_nk_pugl_event_func(PuglView *view, const PuglEvent *e)
+{
+ nk_pugl_window_t *win = puglGetHandle(view);
+ nk_pugl_config_t *cfg = &win->cfg;
+ struct nk_context *ctx = &win->ctx;
+
+ switch(e->type)
+ {
+ case PUGL_NOTHING:
+ {
+ break;
+ }
+ case PUGL_BUTTON_PRESS:
+ {
+ const PuglEventButton *ev = (const PuglEventButton *)e;
+
+ nk_input_button(ctx, ev->button - 1, ev->x, ev->y, 1);
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_BUTTON_RELEASE:
+ {
+ const PuglEventButton *ev = (const PuglEventButton *)e;
+
+ nk_input_button(ctx, ev->button - 1, ev->x, ev->y, 0);
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_CONFIGURE:
+ {
+ const PuglEventConfigure *ev = (const PuglEventConfigure *)e;
+
+ cfg->width = ev->width;
+ cfg->height = ev->height;
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_EXPOSE:
+ {
+ if(win->input_active)
+ {
+ win->input_active = false;
+ nk_input_end(ctx);
+ }
+
+ _nk_pugl_expose(win->view);
+ break;
+ }
+ case PUGL_CLOSE:
+ {
+ nk_pugl_quit(win);
+
+ break;
+ }
+ case PUGL_KEY_PRESS:
+ {
+ const PuglEventKey *ev = (const PuglEventKey *)e;
+
+ _nk_pugl_key(ctx, ev, 1);
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_KEY_RELEASE:
+ {
+ const PuglEventKey *ev = (const PuglEventKey *)e;
+
+ _nk_pugl_key(ctx, ev, 0);
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_MOTION_NOTIFY:
+ {
+ const PuglEventMotion *ev = (const PuglEventMotion *)e;
+
+ nk_input_motion(ctx, ev->x, ev->y);
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_SCROLL:
+ {
+ const PuglEventScroll *ev = (const PuglEventScroll *)e;
+
+ nk_input_scroll(ctx, ev->dy);
+
+ puglPostRedisplay(win->view);
+ break;
+ }
+ case PUGL_ENTER_NOTIFY:
+ // fall-through
+ case PUGL_LEAVE_NOTIFY:
+ // fall-through
+ case PUGL_FOCUS_IN:
+ // fall-through
+ case PUGL_FOCUS_OUT:
+ {
+ puglPostRedisplay(win->view);
+ break;
+ }
+ }
+}
+
+static inline intptr_t
+nk_pugl_init(nk_pugl_window_t *win)
+{
+ nk_pugl_config_t *cfg = &win->cfg;
+ struct nk_convert_config *conv = &win->conv;
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+ win->async = (atomic_flag)ATOMIC_FLAG_INIT;
+ win->disp = XOpenDisplay(0);
+#endif
+
+ // init pugl
+ win->view = puglInit(NULL, NULL);
+ puglInitWindowClass(win->view, cfg->class ? cfg->class : "nuklear");
+ puglInitWindowSize(win->view, cfg->width, cfg->height);
+ puglInitWindowMinSize(win->view, cfg->width, cfg->height);
+ puglInitResizable(win->view, cfg->resizable);
+ puglInitWindowParent(win->view, cfg->parent);
+ puglInitTransientFor(win->view, cfg->parent);
+ puglSetHandle(win->view, win);
+ puglSetEventFunc(win->view, _nk_pugl_dummy_func);
+ puglIgnoreKeyRepeat(win->view, cfg->ignore);
+ puglInitContextType(win->view, PUGL_GL);
+ const int stat = puglCreateWindow(win->view, cfg->title ? cfg->title : "Nuklear");
+ assert(stat == 0);
+
+ puglEnterContext(win->view);
+ {
+ // init nuklear
+ nk_buffer_init_default(&win->cmds);
+ nk_init_default(&win->ctx, 0);
+
+ // init nuklear font
+ struct nk_font *ttf = NULL;
+ struct nk_font_config fcfg = nk_font_config(cfg->font.size);
+ fcfg.range = nk_font_cyrillic_glyph_ranges();
+
+ _nk_pugl_font_stash_begin(win);
+ if(cfg->font.face && cfg->font.size)
+ ttf = nk_font_atlas_add_from_file(&win->atlas, cfg->font.face, cfg->font.size, &fcfg);
+ _nk_pugl_font_stash_end(win);
+ if(ttf)
+ nk_style_set_font(&win->ctx, &ttf->handle);
+
+ win->glGenerateMipmap = GL_EXT(glGenerateMipmap);
+ }
+ puglLeaveContext(win->view, false);
+
+ // fill convert configuration
+ conv->vertex_layout = vertex_layout;
+ conv->vertex_size = sizeof(nk_pugl_vertex_t);
+ conv->vertex_alignment = NK_ALIGNOF(nk_pugl_vertex_t);
+ conv->null = win->null;
+ conv->circle_segment_count = 22;
+ conv->curve_segment_count = 22;
+ conv->arc_segment_count = 22;
+ conv->global_alpha = 1.0f;
+ conv->shape_AA = NK_ANTI_ALIASING_ON;
+ conv->line_AA = NK_ANTI_ALIASING_ON;
+
+ puglSetEventFunc(win->view, _nk_pugl_event_func);
+
+ win->widget = puglGetNativeWindow(win->view);
+ return win->widget;
+}
+
+static inline void
+nk_pugl_show(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return;
+
+ puglShowWindow(win->view);
+}
+
+static inline void
+nk_pugl_hide(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return;
+
+ puglHideWindow(win->view);
+}
+
+static inline void
+nk_pugl_shutdown(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return;
+
+ if(win->last.buffer)
+ free(win->last.buffer);
+
+ puglEnterContext(win->view);
+ {
+ // shutdown nuklear font
+ nk_font_atlas_clear(&win->atlas);
+ if(win->font_tex)
+ glDeleteTextures(1, &win->font_tex);
+
+ // shutdown nuklear
+ nk_free(&win->ctx);
+ nk_buffer_free(&win->cmds);
+ }
+ puglLeaveContext(win->view, false);
+
+ // shutdown pugl
+ puglDestroy(win->view);
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+ if(win->disp)
+ XCloseDisplay(win->disp);
+#endif
+}
+
+static inline void
+nk_pugl_wait_for_event(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return;
+
+ puglWaitForEvent(win->view);
+}
+
+static inline int
+nk_pugl_process_events(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return 1; // quit
+
+ struct nk_context *ctx = &win->ctx;
+
+ if(!win->input_active)
+ {
+ win->input_active = true;
+ nk_input_begin(ctx);
+ }
+
+ PuglStatus stat = puglProcessEvents(win->view);
+ (void)stat;
+
+ return win->quit;
+}
+
+static inline void
+nk_pugl_post_redisplay(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return;
+
+ puglPostRedisplay(win->view);
+}
+
+static inline void
+nk_pugl_async_redisplay(nk_pugl_window_t *win)
+{
+ if(!win->view)
+ return;
+
+#if defined(__APPLE__)
+// TODO
+#endif
+
+#if defined(_WIN32)
+ const HWND widget = (HWND)win->widget;
+ const int status = SendNotifyMessage(widget, WM_PAINT, 0, 0);
+ (void)status;
+#endif
+
+#if !defined(__APPLE__) && !defined(_WIN32)
+ const Window widget = (Window)win->widget;
+ XExposeEvent xevent = {
+ .type = Expose,
+ .display = win->disp,
+ .window = widget
+ };
+
+ while(atomic_flag_test_and_set_explicit(&win->async, memory_order_acquire))
+ {
+ // spin
+ }
+
+ const Status status = XSendEvent(win->disp, widget, false, ExposureMask,
+ (XEvent *)&xevent);
+ (void)status;
+ XFlush(win->disp);
+
+ atomic_flag_clear_explicit(&win->async, memory_order_release);
+#endif
+}
+
+static inline void
+nk_pugl_quit(nk_pugl_window_t *win)
+{
+ win->quit = 1;
+}
+
+static struct nk_image
+nk_pugl_icon_load(nk_pugl_window_t *win, const char *filename)
+{
+ GLuint tex = 0;
+
+ if(!win->view)
+ return nk_image_id(tex);
+
+ int w, h, n;
+ uint8_t *data = stbi_load(filename, &w, &h, &n, 0);
+ if(data)
+ {
+ puglEnterContext(win->view);
+ {
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ if(!win->glGenerateMipmap) // for GL >= 1.4 && < 3.1
+ glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ if(win->glGenerateMipmap) // for GL >= 3.1
+ win->glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ puglLeaveContext(win->view, false);
+
+ stbi_image_free(data);
+ }
+
+ return nk_image_id(tex);
+}
+
+static void
+nk_pugl_icon_unload(nk_pugl_window_t *win, struct nk_image img)
+{
+ if(!win->view)
+ return;
+
+ if(img.handle.id)
+ {
+ puglEnterContext(win->view);
+ {
+ glDeleteTextures(1, (const GLuint *)&img.handle.id);
+ }
+ puglLeaveContext(win->view, false);
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // NK_PUGL_IMPLEMENTATION
diff --git a/.gitattributes b/nuklear/.gitattributes
index 5a5328c..5a5328c 100644
--- a/.gitattributes
+++ b/nuklear/.gitattributes
diff --git a/.gitignore b/nuklear/.gitignore
index a9f3b63..a9f3b63 100644
--- a/.gitignore
+++ b/nuklear/.gitignore
diff --git a/.travis.yml b/nuklear/.travis.yml
index 7df45b3..7df45b3 100644
--- a/.travis.yml
+++ b/nuklear/.travis.yml
diff --git a/CHANGELOG.md b/nuklear/CHANGELOG.md
index 229fbda..229fbda 100644
--- a/CHANGELOG.md
+++ b/nuklear/CHANGELOG.md
diff --git a/Readme.md b/nuklear/Readme.md
index 29d56d3..29d56d3 100644
--- a/Readme.md
+++ b/nuklear/Readme.md
diff --git a/demo/calculator.c b/nuklear/demo/calculator.c
index b871301..b871301 100644
--- a/demo/calculator.c
+++ b/nuklear/demo/calculator.c
diff --git a/demo/d3d11/build.bat b/nuklear/demo/d3d11/build.bat
index 31bd0e0..31bd0e0 100644
--- a/demo/d3d11/build.bat
+++ b/nuklear/demo/d3d11/build.bat
diff --git a/demo/d3d11/main.c b/nuklear/demo/d3d11/main.c
index bc0dc64..bc0dc64 100644
--- a/demo/d3d11/main.c
+++ b/nuklear/demo/d3d11/main.c
diff --git a/demo/d3d11/nuklear_d3d11.h b/nuklear/demo/d3d11/nuklear_d3d11.h
index efddf0d..efddf0d 100644
--- a/demo/d3d11/nuklear_d3d11.h
+++ b/nuklear/demo/d3d11/nuklear_d3d11.h
diff --git a/demo/d3d11/nuklear_d3d11.hlsl b/nuklear/demo/d3d11/nuklear_d3d11.hlsl
index a932dca..a932dca 100644
--- a/demo/d3d11/nuklear_d3d11.hlsl
+++ b/nuklear/demo/d3d11/nuklear_d3d11.hlsl
diff --git a/demo/d3d11/nuklear_d3d11_pixel_shader.h b/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
index 1447559..1447559 100644
--- a/demo/d3d11/nuklear_d3d11_pixel_shader.h
+++ b/nuklear/demo/d3d11/nuklear_d3d11_pixel_shader.h
diff --git a/demo/d3d11/nuklear_d3d11_vertex_shader.h b/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
index 770d2dd..770d2dd 100644
--- a/demo/d3d11/nuklear_d3d11_vertex_shader.h
+++ b/nuklear/demo/d3d11/nuklear_d3d11_vertex_shader.h
diff --git a/demo/gdi/build.bat b/nuklear/demo/gdi/build.bat
index 3884317..3884317 100644
--- a/demo/gdi/build.bat
+++ b/nuklear/demo/gdi/build.bat
diff --git a/demo/gdi/main.c b/nuklear/demo/gdi/main.c
index e82cd16..e82cd16 100644
--- a/demo/gdi/main.c
+++ b/nuklear/demo/gdi/main.c
diff --git a/demo/gdi/nuklear_gdi.h b/nuklear/demo/gdi/nuklear_gdi.h
index 6d3a84a..6d3a84a 100644
--- a/demo/gdi/nuklear_gdi.h
+++ b/nuklear/demo/gdi/nuklear_gdi.h
diff --git a/demo/gdip/build.bat b/nuklear/demo/gdip/build.bat
index 28f51a3..28f51a3 100644
--- a/demo/gdip/build.bat
+++ b/nuklear/demo/gdip/build.bat
diff --git a/demo/gdip/main.c b/nuklear/demo/gdip/main.c
index 7d623e7..7d623e7 100644
--- a/demo/gdip/main.c
+++ b/nuklear/demo/gdip/main.c
diff --git a/demo/gdip/nuklear_gdip.h b/nuklear/demo/gdip/nuklear_gdip.h
index 66ccc0d..66ccc0d 100644
--- a/demo/gdip/nuklear_gdip.h
+++ b/nuklear/demo/gdip/nuklear_gdip.h
diff --git a/demo/glfw_opengl2/Makefile b/nuklear/demo/glfw_opengl2/Makefile
index c937eb8..c937eb8 100644
--- a/demo/glfw_opengl2/Makefile
+++ b/nuklear/demo/glfw_opengl2/Makefile
diff --git a/demo/glfw_opengl2/main.c b/nuklear/demo/glfw_opengl2/main.c
index 1c0f284..1c0f284 100644
--- a/demo/glfw_opengl2/main.c
+++ b/nuklear/demo/glfw_opengl2/main.c
diff --git a/demo/glfw_opengl2/nuklear_glfw_gl2.h b/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
index 93af773..93af773 100644
--- a/demo/glfw_opengl2/nuklear_glfw_gl2.h
+++ b/nuklear/demo/glfw_opengl2/nuklear_glfw_gl2.h
diff --git a/demo/glfw_opengl3/Makefile b/nuklear/demo/glfw_opengl3/Makefile
index 7f620ea..7f620ea 100644
--- a/demo/glfw_opengl3/Makefile
+++ b/nuklear/demo/glfw_opengl3/Makefile
diff --git a/demo/glfw_opengl3/main.c b/nuklear/demo/glfw_opengl3/main.c
index d74ee56..d74ee56 100644
--- a/demo/glfw_opengl3/main.c
+++ b/nuklear/demo/glfw_opengl3/main.c
diff --git a/demo/glfw_opengl3/nuklear_glfw_gl3.h b/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
index aabb365..aabb365 100644
--- a/demo/glfw_opengl3/nuklear_glfw_gl3.h
+++ b/nuklear/demo/glfw_opengl3/nuklear_glfw_gl3.h
diff --git a/demo/node_editor.c b/nuklear/demo/node_editor.c
index 6949f59..6949f59 100644
--- a/demo/node_editor.c
+++ b/nuklear/demo/node_editor.c
diff --git a/demo/overview.c b/nuklear/demo/overview.c
index e271318..e271318 100644
--- a/demo/overview.c
+++ b/nuklear/demo/overview.c
diff --git a/demo/sdl_opengl2/Makefile b/nuklear/demo/sdl_opengl2/Makefile
index 2c85a6e..2c85a6e 100644
--- a/demo/sdl_opengl2/Makefile
+++ b/nuklear/demo/sdl_opengl2/Makefile
diff --git a/demo/sdl_opengl2/main.c b/nuklear/demo/sdl_opengl2/main.c
index 0d96551..0d96551 100644
--- a/demo/sdl_opengl2/main.c
+++ b/nuklear/demo/sdl_opengl2/main.c
diff --git a/demo/sdl_opengl2/nuklear_sdl_gl2.h b/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
index 45c5970..45c5970 100644
--- a/demo/sdl_opengl2/nuklear_sdl_gl2.h
+++ b/nuklear/demo/sdl_opengl2/nuklear_sdl_gl2.h
diff --git a/demo/sdl_opengl3/Makefile b/nuklear/demo/sdl_opengl3/Makefile
index 4b8ccf4..4b8ccf4 100644
--- a/demo/sdl_opengl3/Makefile
+++ b/nuklear/demo/sdl_opengl3/Makefile
diff --git a/demo/sdl_opengl3/main.c b/nuklear/demo/sdl_opengl3/main.c
index aee4f82..aee4f82 100644
--- a/demo/sdl_opengl3/main.c
+++ b/nuklear/demo/sdl_opengl3/main.c
diff --git a/demo/sdl_opengl3/nuklear_sdl_gl3.h b/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
index 17c0899..17c0899 100644
--- a/demo/sdl_opengl3/nuklear_sdl_gl3.h
+++ b/nuklear/demo/sdl_opengl3/nuklear_sdl_gl3.h
diff --git a/demo/style.c b/nuklear/demo/style.c
index 8cea152..8cea152 100644
--- a/demo/style.c
+++ b/nuklear/demo/style.c
diff --git a/demo/x11/Makefile b/nuklear/demo/x11/Makefile
index 0570089..0570089 100644
--- a/demo/x11/Makefile
+++ b/nuklear/demo/x11/Makefile
diff --git a/demo/x11/main.c b/nuklear/demo/x11/main.c
index 8f4b12c..8f4b12c 100644
--- a/demo/x11/main.c
+++ b/nuklear/demo/x11/main.c
diff --git a/demo/x11/nuklear_xlib.h b/nuklear/demo/x11/nuklear_xlib.h
index df66d78..df66d78 100644
--- a/demo/x11/nuklear_xlib.h
+++ b/nuklear/demo/x11/nuklear_xlib.h
diff --git a/demo/x11_opengl2/Makefile b/nuklear/demo/x11_opengl2/Makefile
index 1173b6c..1173b6c 100644
--- a/demo/x11_opengl2/Makefile
+++ b/nuklear/demo/x11_opengl2/Makefile
diff --git a/demo/x11_opengl2/main.c b/nuklear/demo/x11_opengl2/main.c
index cfe5604..cfe5604 100644
--- a/demo/x11_opengl2/main.c
+++ b/nuklear/demo/x11_opengl2/main.c
diff --git a/demo/x11_opengl2/nuklear_xlib_gl2.h b/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
index a814455..a814455 100644
--- a/demo/x11_opengl2/nuklear_xlib_gl2.h
+++ b/nuklear/demo/x11_opengl2/nuklear_xlib_gl2.h
diff --git a/demo/x11_opengl3/Makefile b/nuklear/demo/x11_opengl3/Makefile
index 1173b6c..1173b6c 100644
--- a/demo/x11_opengl3/Makefile
+++ b/nuklear/demo/x11_opengl3/Makefile
diff --git a/demo/x11_opengl3/main.c b/nuklear/demo/x11_opengl3/main.c
index cd026f9..cd026f9 100644
--- a/demo/x11_opengl3/main.c
+++ b/nuklear/demo/x11_opengl3/main.c
diff --git a/demo/x11_opengl3/nuklear_xlib_gl3.h b/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
index b0f56b9..b0f56b9 100644
--- a/demo/x11_opengl3/nuklear_xlib_gl3.h
+++ b/nuklear/demo/x11_opengl3/nuklear_xlib_gl3.h
diff --git a/example/Makefile b/nuklear/example/Makefile
index 22829a2..22829a2 100644
--- a/example/Makefile
+++ b/nuklear/example/Makefile
diff --git a/example/canvas.c b/nuklear/example/canvas.c
index c5f5634..c5f5634 100644
--- a/example/canvas.c
+++ b/nuklear/example/canvas.c
diff --git a/example/extended.c b/nuklear/example/extended.c
index e555d65..e555d65 100644
--- a/example/extended.c
+++ b/nuklear/example/extended.c
diff --git a/example/file_browser.c b/nuklear/example/file_browser.c
index ece4cb8..ece4cb8 100644
--- a/example/file_browser.c
+++ b/nuklear/example/file_browser.c
diff --git a/example/icon/checked.png b/nuklear/example/icon/checked.png
index e4e05b2..e4e05b2 100644
--- a/example/icon/checked.png
+++ b/nuklear/example/icon/checked.png
Binary files differ
diff --git a/example/icon/cloud.png b/nuklear/example/icon/cloud.png
index ecc5791..ecc5791 100644
--- a/example/icon/cloud.png
+++ b/nuklear/example/icon/cloud.png
Binary files differ
diff --git a/example/icon/computer.png b/nuklear/example/icon/computer.png
index 29db8fc..29db8fc 100644
--- a/example/icon/computer.png
+++ b/nuklear/example/icon/computer.png
Binary files differ
diff --git a/example/icon/copy.png b/nuklear/example/icon/copy.png
index 0a6e979..0a6e979 100644
--- a/example/icon/copy.png
+++ b/nuklear/example/icon/copy.png
Binary files differ
diff --git a/example/icon/default.png b/nuklear/example/icon/default.png
index c11145a..c11145a 100644
--- a/example/icon/default.png
+++ b/nuklear/example/icon/default.png
Binary files differ
diff --git a/example/icon/delete.png b/nuklear/example/icon/delete.png
index 7bc6dde..7bc6dde 100644
--- a/example/icon/delete.png
+++ b/nuklear/example/icon/delete.png
Binary files differ
diff --git a/example/icon/desktop.png b/nuklear/example/icon/desktop.png
index b4abcfd..b4abcfd 100644
--- a/example/icon/desktop.png
+++ b/nuklear/example/icon/desktop.png
Binary files differ
diff --git a/example/icon/directory.png b/nuklear/example/icon/directory.png
index 4c73d37..4c73d37 100644
--- a/example/icon/directory.png
+++ b/nuklear/example/icon/directory.png
Binary files differ
diff --git a/example/icon/edit.png b/nuklear/example/icon/edit.png
index 62ce0b4..62ce0b4 100644
--- a/example/icon/edit.png
+++ b/nuklear/example/icon/edit.png
Binary files differ
diff --git a/example/icon/export.png b/nuklear/example/icon/export.png
index ff6b5aa..ff6b5aa 100644
--- a/example/icon/export.png
+++ b/nuklear/example/icon/export.png
Binary files differ
diff --git a/example/icon/font.png b/nuklear/example/icon/font.png
index 918e9bf..918e9bf 100644
--- a/example/icon/font.png
+++ b/nuklear/example/icon/font.png
Binary files differ
diff --git a/example/icon/home.png b/nuklear/example/icon/home.png
index 8560626..8560626 100644
--- a/example/icon/home.png
+++ b/nuklear/example/icon/home.png
Binary files differ
diff --git a/example/icon/img.png b/nuklear/example/icon/img.png
index 1985957..1985957 100644
--- a/example/icon/img.png
+++ b/nuklear/example/icon/img.png
Binary files differ
diff --git a/example/icon/movie.png b/nuklear/example/icon/movie.png
index 5227883..5227883 100644
--- a/example/icon/movie.png
+++ b/nuklear/example/icon/movie.png
Binary files differ
diff --git a/example/icon/music.png b/nuklear/example/icon/music.png
index 0f1415c..0f1415c 100644
--- a/example/icon/music.png
+++ b/nuklear/example/icon/music.png
Binary files differ
diff --git a/example/icon/next.png b/nuklear/example/icon/next.png
index af0b98d..af0b98d 100644
--- a/example/icon/next.png
+++ b/nuklear/example/icon/next.png
Binary files differ
diff --git a/example/icon/pause.png b/nuklear/example/icon/pause.png
index 7d6367e..7d6367e 100644
--- a/example/icon/pause.png
+++ b/nuklear/example/icon/pause.png
Binary files differ
diff --git a/example/icon/pen.png b/nuklear/example/icon/pen.png
index 10c851c..10c851c 100644
--- a/example/icon/pen.png
+++ b/nuklear/example/icon/pen.png
Binary files differ
diff --git a/example/icon/phone.png b/nuklear/example/icon/phone.png
index 5e6f613..5e6f613 100644
--- a/example/icon/phone.png
+++ b/nuklear/example/icon/phone.png
Binary files differ
diff --git a/example/icon/plane.png b/nuklear/example/icon/plane.png
index 3a98489..3a98489 100644
--- a/example/icon/plane.png
+++ b/nuklear/example/icon/plane.png
Binary files differ
diff --git a/example/icon/play.png b/nuklear/example/icon/play.png
index 9c9e8f0..9c9e8f0 100644
--- a/example/icon/play.png
+++ b/nuklear/example/icon/play.png
Binary files differ
diff --git a/example/icon/prev.png b/nuklear/example/icon/prev.png
index 0eecc2e..0eecc2e 100644
--- a/example/icon/prev.png
+++ b/nuklear/example/icon/prev.png
Binary files differ
diff --git a/example/icon/rocket.png b/nuklear/example/icon/rocket.png
index ea8e187..ea8e187 100644
--- a/example/icon/rocket.png
+++ b/nuklear/example/icon/rocket.png
Binary files differ
diff --git a/example/icon/settings.png b/nuklear/example/icon/settings.png
index e6e13f8..e6e13f8 100644
--- a/example/icon/settings.png
+++ b/nuklear/example/icon/settings.png
Binary files differ
diff --git a/example/icon/stop.png b/nuklear/example/icon/stop.png
index 6742baf..6742baf 100644
--- a/example/icon/stop.png
+++ b/nuklear/example/icon/stop.png
Binary files differ
diff --git a/example/icon/text.png b/nuklear/example/icon/text.png
index 136e534..136e534 100644
--- a/example/icon/text.png
+++ b/nuklear/example/icon/text.png
Binary files differ
diff --git a/example/icon/tools.png b/nuklear/example/icon/tools.png
index 412ff85..412ff85 100644
--- a/example/icon/tools.png
+++ b/nuklear/example/icon/tools.png
Binary files differ
diff --git a/example/icon/unchecked.png b/nuklear/example/icon/unchecked.png
index fca94d2..fca94d2 100644
--- a/example/icon/unchecked.png
+++ b/nuklear/example/icon/unchecked.png
Binary files differ
diff --git a/example/icon/volume.png b/nuklear/example/icon/volume.png
index 8e86fa9..8e86fa9 100644
--- a/example/icon/volume.png
+++ b/nuklear/example/icon/volume.png
Binary files differ
diff --git a/example/icon/wifi.png b/nuklear/example/icon/wifi.png
index 270d55d..270d55d 100644
--- a/example/icon/wifi.png
+++ b/nuklear/example/icon/wifi.png
Binary files differ
diff --git a/example/images/image1.png b/nuklear/example/images/image1.png
index 66a2b63..66a2b63 100644
--- a/example/images/image1.png
+++ b/nuklear/example/images/image1.png
Binary files differ
diff --git a/example/images/image2.png b/nuklear/example/images/image2.png
index 4acafe5..4acafe5 100644
--- a/example/images/image2.png
+++ b/nuklear/example/images/image2.png
Binary files differ
diff --git a/example/images/image3.png b/nuklear/example/images/image3.png
index 4dfe664..4dfe664 100644
--- a/example/images/image3.png
+++ b/nuklear/example/images/image3.png
Binary files differ
diff --git a/example/images/image4.png b/nuklear/example/images/image4.png
index d2f16d0..d2f16d0 100644
--- a/example/images/image4.png
+++ b/nuklear/example/images/image4.png
Binary files differ
diff --git a/example/images/image5.png b/nuklear/example/images/image5.png
index 852fd70..852fd70 100644
--- a/example/images/image5.png
+++ b/nuklear/example/images/image5.png
Binary files differ
diff --git a/example/images/image6.png b/nuklear/example/images/image6.png
index 0e261cb..0e261cb 100644
--- a/example/images/image6.png
+++ b/nuklear/example/images/image6.png
Binary files differ
diff --git a/example/images/image7.png b/nuklear/example/images/image7.png
index f61325b..f61325b 100644
--- a/example/images/image7.png
+++ b/nuklear/example/images/image7.png
Binary files differ
diff --git a/example/images/image8.png b/nuklear/example/images/image8.png
index 6b27cb8..6b27cb8 100644
--- a/example/images/image8.png
+++ b/nuklear/example/images/image8.png
Binary files differ
diff --git a/example/images/image9.png b/nuklear/example/images/image9.png
index 516929e..516929e 100644
--- a/example/images/image9.png
+++ b/nuklear/example/images/image9.png
Binary files differ
diff --git a/example/skinning.c b/nuklear/example/skinning.c
index 4634b09..4634b09 100644
--- a/example/skinning.c
+++ b/nuklear/example/skinning.c
diff --git a/example/skins/gwen.png b/nuklear/example/skins/gwen.png
index 40956c9..40956c9 100644
--- a/example/skins/gwen.png
+++ b/nuklear/example/skins/gwen.png
Binary files differ
diff --git a/example/stb_image.h b/nuklear/example/stb_image.h
index 0a9de39..0a9de39 100644
--- a/example/stb_image.h
+++ b/nuklear/example/stb_image.h
diff --git a/extra_font/Cousine-Regular.ttf b/nuklear/extra_font/Cousine-Regular.ttf
index 70a0bf9..70a0bf9 100644
--- a/extra_font/Cousine-Regular.ttf
+++ b/nuklear/extra_font/Cousine-Regular.ttf
Binary files differ
diff --git a/extra_font/DroidSans.ttf b/nuklear/extra_font/DroidSans.ttf
index 767c63a..767c63a 100644
--- a/extra_font/DroidSans.ttf
+++ b/nuklear/extra_font/DroidSans.ttf
Binary files differ
diff --git a/extra_font/Karla-Regular.ttf b/nuklear/extra_font/Karla-Regular.ttf
index 81b3de6..81b3de6 100644
--- a/extra_font/Karla-Regular.ttf
+++ b/nuklear/extra_font/Karla-Regular.ttf
Binary files differ
diff --git a/extra_font/ProggyClean.ttf b/nuklear/extra_font/ProggyClean.ttf
index 0270cdf..0270cdf 100644
--- a/extra_font/ProggyClean.ttf
+++ b/nuklear/extra_font/ProggyClean.ttf
Binary files differ
diff --git a/extra_font/ProggyTiny.ttf b/nuklear/extra_font/ProggyTiny.ttf
index 1c4312c..1c4312c 100644
--- a/extra_font/ProggyTiny.ttf
+++ b/nuklear/extra_font/ProggyTiny.ttf
Binary files differ
diff --git a/extra_font/Raleway-Bold.ttf b/nuklear/extra_font/Raleway-Bold.ttf
index 7aa37f0..7aa37f0 100644
--- a/extra_font/Raleway-Bold.ttf
+++ b/nuklear/extra_font/Raleway-Bold.ttf
Binary files differ
diff --git a/extra_font/Roboto-Bold.ttf b/nuklear/extra_font/Roboto-Bold.ttf
index aaf374d..aaf374d 100644
--- a/extra_font/Roboto-Bold.ttf
+++ b/nuklear/extra_font/Roboto-Bold.ttf
Binary files differ
diff --git a/extra_font/Roboto-Light.ttf b/nuklear/extra_font/Roboto-Light.ttf
index 664e1b2..664e1b2 100644
--- a/extra_font/Roboto-Light.ttf
+++ b/nuklear/extra_font/Roboto-Light.ttf
Binary files differ
diff --git a/extra_font/Roboto-Regular.ttf b/nuklear/extra_font/Roboto-Regular.ttf
index 3e6e2e7..3e6e2e7 100644
--- a/extra_font/Roboto-Regular.ttf
+++ b/nuklear/extra_font/Roboto-Regular.ttf
Binary files differ
diff --git a/extra_font/kenvector_future.ttf b/nuklear/extra_font/kenvector_future.ttf
index 39ebdfa..39ebdfa 100644
--- a/extra_font/kenvector_future.ttf
+++ b/nuklear/extra_font/kenvector_future.ttf
Binary files differ
diff --git a/extra_font/kenvector_future_thin.ttf b/nuklear/extra_font/kenvector_future_thin.ttf
index 9f4b4fa..9f4b4fa 100644
--- a/extra_font/kenvector_future_thin.ttf
+++ b/nuklear/extra_font/kenvector_future_thin.ttf
Binary files differ
diff --git a/nuklear.h b/nuklear/nuklear.h
index c37e328..c37e328 100644
--- a/nuklear.h
+++ b/nuklear/nuklear.h
diff --git a/package.json b/nuklear/package.json
index edff924..edff924 100644
--- a/package.json
+++ b/nuklear/package.json
diff --git a/osc.lv2/CMakeLists.txt b/osc.lv2/CMakeLists.txt
new file mode 100644
index 0000000..64ce73b
--- /dev/null
+++ b/osc.lv2/CMakeLists.txt
@@ -0,0 +1,16 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(osc.lv2)
+
+include_directories(${PROJECT_SOURCE_DIR})
+
+set(CMAKE_C_FLAGS "-std=gnu11 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
+
+include(CTest)
+
+if(${BUILD_TESTING})
+ add_executable(osc_test
+ osc_test.c)
+ add_test(NAME API-Test COMMAND osc_test)
+endif()
diff --git a/osc.lv2/COPYING b/osc.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/osc.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/osc.lv2/README.md b/osc.lv2/README.md
new file mode 100644
index 0000000..b48021f
--- /dev/null
+++ b/osc.lv2/README.md
@@ -0,0 +1,3 @@
+# osc.lv2
+
+## Open Sound Control Extension for the LV2 Plugin Specification
diff --git a/osc.lv2/lv2-osc.doap.ttl b/osc.lv2/lv2-osc.doap.ttl
new file mode 100644
index 0000000..ef74f92
--- /dev/null
+++ b/osc.lv2/lv2-osc.doap.ttl
@@ -0,0 +1,40 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix dcs: <http://ontologi.es/doap-changeset#> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a doap:Project ;
+ doap:license lic:Artistic-2.0 ;
+ doap:name "LV2 OSC" ;
+ doap:shortdesc "A definition of atomified OSC." ;
+ doap:maintainer omk:me ;
+ doap:created "2015-06-19" ;
+ doap:developer omk:me ;
+ doap:release [
+ doap:revision "1.0" ;
+ doap:created "2015-06-19" ;
+ dcs:blame omk:me ;
+ dcs:changeset [
+ dcs:item [
+ rdfs:label "Initial release."
+ ]
+ ]
+ ] .
diff --git a/osc.lv2/manifest.ttl b/osc.lv2/manifest.ttl
new file mode 100644
index 0000000..a2bbaf8
--- /dev/null
+++ b/osc.lv2/manifest.ttl
@@ -0,0 +1,23 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a lv2:Specification ;
+ lv2:minorVersion 1 ;
+ lv2:microVersion 0 ;
+ rdfs:seeAlso <osc.ttl> .
diff --git a/osc.lv2/osc.lv2/endian.h b/osc.lv2/osc.lv2/endian.h
new file mode 100644
index 0000000..a7aad4a
--- /dev/null
+++ b/osc.lv2/osc.lv2/endian.h
@@ -0,0 +1,129 @@
+// "License": Public Domain
+// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+#ifndef PORTABLE_ENDIAN_H__
+#define PORTABLE_ENDIAN_H__
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+# define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__)
+
+# include <endian.h>
+
+#elif defined(__APPLE__)
+
+# include <libkern/OSByteOrder.h>
+
+# define htobe16(x) OSSwapHostToBigInt16(x)
+# define htole16(x) OSSwapHostToLittleInt16(x)
+# define be16toh(x) OSSwapBigToHostInt16(x)
+# define le16toh(x) OSSwapLittleToHostInt16(x)
+
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define htole32(x) OSSwapHostToLittleInt32(x)
+# define be32toh(x) OSSwapBigToHostInt32(x)
+# define le32toh(x) OSSwapLittleToHostInt32(x)
+
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define htole64(x) OSSwapHostToLittleInt64(x)
+# define be64toh(x) OSSwapBigToHostInt64(x)
+# define le64toh(x) OSSwapLittleToHostInt64(x)
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+# include <sys/endian.h>
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+
+# include <sys/endian.h>
+
+# define be16toh(x) betoh16(x)
+# define le16toh(x) letoh16(x)
+
+# define be32toh(x) betoh32(x)
+# define le32toh(x) letoh32(x)
+
+# define be64toh(x) betoh64(x)
+# define le64toh(x) letoh64(x)
+
+#elif defined(__WINDOWS__)
+
+# include <winsock2.h>
+# include <sys/param.h>
+
+# if BYTE_ORDER == LITTLE_ENDIAN
+
+# define htobe16(x) htons(x)
+# define htole16(x) (x)
+# define be16toh(x) ntohs(x)
+# define le16toh(x) (x)
+
+# define htobe32(x) htonl(x)
+# define htole32(x) (x)
+# define be32toh(x) ntohl(x)
+# define le32toh(x) (x)
+
+# ifndef htonll
+static inline uint64_t htonll(uint64_t n)
+{
+ return (((uint64_t)htonl(n)) << 32) + htonl(n >> 32);
+}
+# endif
+
+# ifndef ntohll
+# define ntohll htonll
+# endif
+
+# define htobe64(x) htonll(x)
+# define htole64(x) (x)
+# define be64toh(x) ntohll(x)
+# define le64toh(x) (x)
+
+# elif BYTE_ORDER == BIG_ENDIAN
+
+ /* that would be xbox 360 */
+# define htobe16(x) (x)
+# define htole16(x) __builtin_bswap16(x)
+# define be16toh(x) (x)
+# define le16toh(x) __builtin_bswap16(x)
+
+# define htobe32(x) (x)
+# define htole32(x) __builtin_bswap32(x)
+# define be32toh(x) (x)
+# define le32toh(x) __builtin_bswap32(x)
+
+# define htobe64(x) (x)
+# define htole64(x) __builtin_bswap64(x)
+# define be64toh(x) (x)
+# define le64toh(x) __builtin_bswap64(x)
+
+# else
+
+# error byte order not supported
+
+# endif
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#else
+
+# error platform not supported
+
+#endif
+
+#endif
diff --git a/osc.lv2/osc.lv2/forge.h b/osc.lv2/osc.lv2/forge.h
new file mode 100644
index 0000000..6dc5fe7
--- /dev/null
+++ b/osc.lv2/osc.lv2/forge.h
@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_FORGE_H
+#define LV2_OSC_FORGE_H
+
+#include <inttypes.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/reader.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define lv2_osc_forge_int(forge, osc_urid, val) \
+ lv2_atom_forge_int((forge), (val))
+
+#define lv2_osc_forge_float(forge, osc_urid, val) \
+ lv2_atom_forge_float((forge), (val))
+
+#define lv2_osc_forge_string(forge, osc_urid, val, len) \
+ lv2_atom_forge_string((forge), (val), (len))
+
+#define lv2_osc_forge_long(forge, osc_urid, val) \
+ lv2_atom_forge_long((forge), (val))
+
+#define lv2_osc_forge_double(forge, osc_urid, val) \
+ lv2_atom_forge_double((forge), (val))
+
+#define lv2_osc_forge_true(forge, osc_urid) \
+ lv2_atom_forge_bool((forge), 1)
+
+#define lv2_osc_forge_false(forge, osc_urid) \
+ lv2_atom_forge_bool((forge), 0)
+
+#define lv2_osc_forge_nil(forge, osc_urid) \
+ lv2_atom_forge_literal((forge), "", 0, (osc_urid)->OSC_Nil, 0)
+
+#define lv2_osc_forge_impulse(forge, osc_urid) \
+ lv2_atom_forge_literal((forge), "", 0, (osc_urid)->OSC_Impulse, 0)
+
+#define lv2_osc_forge_symbol(forge, osc_urid, val) \
+ lv2_atom_forge_urid((forge), (val))
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_chunk(LV2_Atom_Forge *forge, LV2_URID type,
+ const uint8_t *buf, uint32_t size)
+{
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_atom(forge, size, type))
+ && (ref = lv2_atom_forge_raw(forge, buf, size)) )
+ {
+ lv2_atom_forge_pad(forge, size);
+ return ref;
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_midi(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const uint8_t *buf, uint32_t size)
+{
+ assert(size <= 3);
+ return lv2_osc_forge_chunk(forge, osc_urid->MIDI_MidiEvent, buf, size);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_blob(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ const uint8_t *buf, uint32_t size)
+{
+ return lv2_osc_forge_chunk(forge, osc_urid->ATOM_Chunk, buf, size);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_char(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ char val)
+{
+ return lv2_atom_forge_literal(forge, &val, 1, osc_urid->OSC_Char, 0);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_rgba(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ char val [9];
+ sprintf(val, "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8, r, g, b, a);
+ return lv2_atom_forge_literal(forge, val, 8, osc_urid->OSC_RGBA, 0);
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_timetag(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const LV2_OSC_Timetag *timetag)
+{
+ LV2_Atom_Forge_Frame frame;
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_object(forge, &frame, 0, osc_urid->OSC_Timetag))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_timetagIntegral))
+ && (ref = lv2_atom_forge_long(forge, timetag->integral))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_timetagFraction))
+ && (ref = lv2_atom_forge_long(forge, timetag->fraction)) )
+ {
+ lv2_atom_forge_pop(forge, &frame);
+ return ref;
+ }
+
+ return 0;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_bundle_head(LV2_Atom_Forge* forge, LV2_OSC_URID *osc_urid,
+ LV2_Atom_Forge_Frame frame [2], const LV2_OSC_Timetag *timetag)
+{
+ LV2_Atom_Forge_Ref ref;
+
+ if( (ref = lv2_atom_forge_object(forge, &frame[0], 0, osc_urid->OSC_Bundle))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_bundleTimetag))
+ && (ref = lv2_osc_forge_timetag(forge, osc_urid, timetag))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_bundleItems))
+ && (ref = lv2_atom_forge_tuple(forge, &frame[1])) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+/**
+ TODO
+*/
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_head(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ LV2_Atom_Forge_Frame frame [2], const char *path)
+{
+ assert(path);
+
+ LV2_Atom_Forge_Ref ref;
+ if( (ref = lv2_atom_forge_object(forge, &frame[0], 0, osc_urid->OSC_Message))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_messagePath))
+ && (ref = lv2_atom_forge_string(forge, path, strlen(path)))
+ && (ref = lv2_atom_forge_key(forge, osc_urid->OSC_messageArguments))
+ && (ref = lv2_atom_forge_tuple(forge, &frame[1])) )
+ {
+ return ref;
+ }
+
+ return 0;
+}
+
+/**
+ TODO
+*/
+static inline void
+lv2_osc_forge_pop(LV2_Atom_Forge *forge, LV2_Atom_Forge_Frame frame [2])
+{
+ lv2_atom_forge_pop(forge, &frame[1]); // a LV2_Atom_Tuple
+ lv2_atom_forge_pop(forge, &frame[0]); // a LV2_Atom_Object
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_varlist(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const char *path, const char *fmt, va_list args)
+{
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ if(!lv2_osc_check_path(path) || !lv2_osc_check_fmt(fmt, 0))
+ return 0;
+ if(!(ref = lv2_osc_forge_message_head(forge, osc_urid, frame, path)))
+ return 0;
+
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!(ref = lv2_osc_forge_int(forge, osc_urid, va_arg(args, int32_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!(ref = lv2_osc_forge_float(forge, osc_urid, (float)va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ const char *s = va_arg(args, const char *);
+ if(!s || !(ref = lv2_osc_forge_string(forge, osc_urid, s, strlen(s))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ const int32_t size = va_arg(args, int32_t);
+ const uint8_t *b = va_arg(args, const uint8_t *);
+ if(!b || !(ref = lv2_osc_forge_blob(forge, osc_urid, b, size)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_INT64:
+ {
+ if(!(ref = lv2_osc_forge_long(forge, osc_urid, va_arg(args, int64_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!(ref = lv2_osc_forge_double(forge, osc_urid, va_arg(args, double))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ const LV2_OSC_Timetag timetag = {
+ .integral = va_arg(args, uint32_t),
+ .fraction = va_arg(args, uint32_t)
+ };
+ if(!(ref = lv2_osc_forge_timetag(forge, osc_urid, &timetag)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ if(!(ref = lv2_osc_forge_true(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ if(!(ref = lv2_osc_forge_false(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ if(!(ref = lv2_osc_forge_nil(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ if(!(ref = lv2_osc_forge_impulse(forge, osc_urid)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ if(!(ref = lv2_osc_forge_symbol(forge, osc_urid, va_arg(args, uint32_t))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ const int32_t size = va_arg(args, int32_t);
+ const uint8_t *m = va_arg(args, const uint8_t *);
+ if(!m || !(ref = lv2_osc_forge_midi(forge, osc_urid, m, size)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!(ref = lv2_osc_forge_char(forge, osc_urid, (char)va_arg(args, int))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!(ref = lv2_osc_forge_rgba(forge, osc_urid,
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned),
+ (uint8_t)va_arg(args, unsigned))))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_message_vararg(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ const char *path, const char *fmt, ...)
+{
+ LV2_Atom_Forge_Ref ref;
+ va_list args;
+
+ va_start(args, fmt);
+
+ ref = lv2_osc_forge_message_varlist(forge, osc_urid, path, fmt, args);
+
+ va_end(args);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+lv2_osc_forge_packet(LV2_Atom_Forge *forge, LV2_OSC_URID *osc_urid,
+ LV2_URID_Map *map, const uint8_t *buf, size_t size)
+{
+ LV2_OSC_Reader reader;
+ LV2_Atom_Forge_Frame frame [2];
+ LV2_Atom_Forge_Ref ref;
+
+ lv2_osc_reader_initialize(&reader, buf, size);
+
+ if(lv2_osc_reader_is_bundle(&reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(&reader, size);
+
+ if(itm && (ref = lv2_osc_forge_bundle_head(forge, osc_urid, frame,
+ LV2_OSC_TIMETAG_CREATE(itm->timetag))))
+ {
+ OSC_READER_BUNDLE_ITERATE(&reader, itm)
+ {
+ if(!(ref = lv2_osc_forge_packet(forge, osc_urid, map, itm->body, itm->size)))
+ return 0;
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+ }
+ }
+ else if(lv2_osc_reader_is_message(&reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(&reader, size);
+
+ if(arg && (ref = lv2_osc_forge_message_head(forge, osc_urid, frame, arg->path)))
+ {
+ OSC_READER_MESSAGE_ITERATE(&reader, arg)
+ {
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!(ref = lv2_osc_forge_int(forge, osc_urid, arg->i)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!(ref = lv2_osc_forge_float(forge, osc_urid, arg->f)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!(ref = lv2_osc_forge_string(forge, osc_urid, arg->s, arg->size - 1)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!(ref = lv2_osc_forge_blob(forge, osc_urid, arg->b, arg->size)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_INT64:
+ {
+ if(!(ref = lv2_osc_forge_long(forge, osc_urid, arg->h)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!(ref = lv2_osc_forge_double(forge, osc_urid, arg->d)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!(ref = lv2_osc_forge_timetag(forge, osc_urid, LV2_OSC_TIMETAG_CREATE(arg->t))))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ {
+ if(!(ref = lv2_osc_forge_true(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_FALSE:
+ {
+ if(!(ref = lv2_osc_forge_false(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_NIL:
+ {
+ if(!(ref = lv2_osc_forge_nil(forge, osc_urid)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_IMPULSE:
+ {
+ if(!(ref = lv2_osc_forge_impulse(forge, osc_urid)))
+ return 0;
+ break;
+ }
+
+ case LV2_OSC_SYMBOL:
+ {
+ if(!(ref = lv2_osc_forge_symbol(forge, osc_urid,
+ map->map(map->handle, arg->S))))
+ return 0;
+ break;
+ }
+ case LV2_OSC_MIDI:
+ {
+ if(!(ref = lv2_osc_forge_midi(forge, osc_urid, &arg->b[1], arg->size - 1)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!(ref = lv2_osc_forge_char(forge, osc_urid, arg->c)))
+ return 0;
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!(ref = lv2_osc_forge_rgba(forge, osc_urid, arg->R, arg->G, arg->B, arg->A)))
+ return 0;
+ break;
+ }
+ }
+ }
+
+ lv2_osc_forge_pop(forge, frame);
+
+ return ref;
+ }
+ }
+
+ return 0;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_FORGE_H
diff --git a/osc.lv2/osc.lv2/osc.h b/osc.lv2/osc.lv2/osc.h
new file mode 100644
index 0000000..1ada68c
--- /dev/null
+++ b/osc.lv2/osc.lv2/osc.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_H
+#define LV2_OSC_H
+
+#include <stdint.h>
+
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
+
+#define LV2_OSC_URI "http://open-music-kontrollers.ch/lv2/osc"
+#define LV2_OSC_PREFIX LV2_OSC_URI "#"
+
+#define LV2_OSC__Event LV2_OSC_PREFIX "Event" // atom message type
+#define LV2_OSC__schedule LV2_OSC_PREFIX "schedule" // feature
+
+#define LV2_OSC__Packet LV2_OSC_PREFIX "Packet" // atom object type
+
+#define LV2_OSC__Bundle LV2_OSC_PREFIX "Bundle" // atom object type
+#define LV2_OSC__bundleTimetag LV2_OSC_PREFIX "bundleTimetag" // atom object property
+#define LV2_OSC__bundleItems LV2_OSC_PREFIX "bundleItems"
+
+#define LV2_OSC__Message LV2_OSC_PREFIX "Message" // atom object type
+#define LV2_OSC__messagePath LV2_OSC_PREFIX "messagePath" // atom object property
+#define LV2_OSC__messageArguments LV2_OSC_PREFIX "messageArguments" // atom object property
+
+#define LV2_OSC__Timetag LV2_OSC_PREFIX "Timetag" // atom object type
+#define LV2_OSC__timetagIntegral LV2_OSC_PREFIX "timetagIntegral" // atom object property
+#define LV2_OSC__timetagFraction LV2_OSC_PREFIX "timetagFraction" // atom object property
+
+#define LV2_OSC__Nil LV2_OSC_PREFIX "Nil" // atom literal type
+#define LV2_OSC__Impulse LV2_OSC_PREFIX "Impulse" // atom literal type
+#define LV2_OSC__Char LV2_OSC_PREFIX "Char" // atom literal type
+#define LV2_OSC__RGBA LV2_OSC_PREFIX "RGBA" // atom literal type
+
+#define LV2_OSC_PADDED_SIZE(size) ( ( (size_t)(size) + 3 ) & ( ~3 ) )
+#define LV2_OSC_IMMEDIATE 1ULL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *LV2_OSC_Schedule_Handle;
+
+typedef double (*LV2_OSC_Schedule_OSC2Frames)(
+ LV2_OSC_Schedule_Handle handle,
+ uint64_t timetag);
+
+typedef uint64_t (*LV2_OSC_Schedule_Frames2OSC)(
+ LV2_OSC_Schedule_Handle handle,
+ double frames);
+
+typedef struct _LV2_OSC_Schedule {
+ LV2_OSC_Schedule_Handle handle;
+ LV2_OSC_Schedule_OSC2Frames osc2frames;
+ LV2_OSC_Schedule_Frames2OSC frames2osc;
+} LV2_OSC_Schedule;
+
+typedef enum LV2_OSC_Type {
+ LV2_OSC_INT32 = 'i',
+ LV2_OSC_FLOAT = 'f',
+ LV2_OSC_STRING = 's',
+ LV2_OSC_BLOB = 'b',
+
+ LV2_OSC_TRUE = 'T',
+ LV2_OSC_FALSE = 'F',
+ LV2_OSC_NIL = 'N',
+ LV2_OSC_IMPULSE = 'I',
+
+ LV2_OSC_INT64 = 'h',
+ LV2_OSC_DOUBLE = 'd',
+ LV2_OSC_TIMETAG = 't',
+
+ LV2_OSC_SYMBOL = 'S',
+ LV2_OSC_CHAR = 'c',
+ LV2_OSC_MIDI = 'm',
+ LV2_OSC_RGBA = 'r'
+} LV2_OSC_Type;
+
+union swap32_t {
+ uint32_t u;
+
+ int32_t i;
+ float f;
+};
+
+union swap64_t {
+ uint64_t u;
+
+ int64_t h;
+ uint64_t t;
+ double d;
+};
+
+typedef struct _LV2_OSC_Timetag {
+ uint32_t integral;
+ uint32_t fraction;
+} LV2_OSC_Timetag;
+
+typedef struct _LV2_OSC_URID {
+ LV2_URID OSC_Packet;
+
+ LV2_URID OSC_Bundle;
+ LV2_URID OSC_bundleTimetag;
+ LV2_URID OSC_bundleItems;
+
+ LV2_URID OSC_Message;
+ LV2_URID OSC_messagePath;
+ LV2_URID OSC_messageArguments;
+
+ LV2_URID OSC_Timetag;
+ LV2_URID OSC_timetagIntegral;
+ LV2_URID OSC_timetagFraction;
+
+ LV2_URID OSC_Nil;
+ LV2_URID OSC_Impulse;
+ LV2_URID OSC_Char;
+ LV2_URID OSC_RGBA;
+
+ LV2_URID MIDI_MidiEvent;
+
+ LV2_URID ATOM_Int;
+ LV2_URID ATOM_Long;
+ LV2_URID ATOM_String;
+ LV2_URID ATOM_Literal;
+ LV2_URID ATOM_Float;
+ LV2_URID ATOM_Double;
+ LV2_URID ATOM_URID;
+ LV2_URID ATOM_Bool;
+ LV2_URID ATOM_Tuple;
+ LV2_URID ATOM_Object;
+ LV2_URID ATOM_Chunk;
+} LV2_OSC_URID;
+
+static inline void
+lv2_osc_urid_init(LV2_OSC_URID *osc_urid, LV2_URID_Map *map)
+{
+ osc_urid->OSC_Packet = map->map(map->handle, LV2_OSC__Packet);
+
+ osc_urid->OSC_Bundle = map->map(map->handle, LV2_OSC__Bundle);
+ osc_urid->OSC_bundleTimetag = map->map(map->handle, LV2_OSC__bundleTimetag);
+ osc_urid->OSC_bundleItems = map->map(map->handle, LV2_OSC__bundleItems);
+
+ osc_urid->OSC_Message = map->map(map->handle, LV2_OSC__Message);
+ osc_urid->OSC_messagePath = map->map(map->handle, LV2_OSC__messagePath);
+ osc_urid->OSC_messageArguments = map->map(map->handle, LV2_OSC__messageArguments);
+
+ osc_urid->OSC_Timetag = map->map(map->handle, LV2_OSC__Timetag);
+ osc_urid->OSC_timetagIntegral = map->map(map->handle, LV2_OSC__timetagIntegral);
+ osc_urid->OSC_timetagFraction = map->map(map->handle, LV2_OSC__timetagFraction);
+
+ osc_urid->OSC_Nil = map->map(map->handle, LV2_OSC__Nil);
+ osc_urid->OSC_Impulse = map->map(map->handle, LV2_OSC__Impulse);
+ osc_urid->OSC_Char = map->map(map->handle, LV2_OSC__Char);
+ osc_urid->OSC_RGBA = map->map(map->handle, LV2_OSC__RGBA);
+
+ osc_urid->MIDI_MidiEvent = map->map(map->handle, LV2_MIDI__MidiEvent);
+
+ osc_urid->ATOM_Int = map->map(map->handle, LV2_ATOM__Int);
+ osc_urid->ATOM_Long = map->map(map->handle, LV2_ATOM__Long);
+ osc_urid->ATOM_String = map->map(map->handle, LV2_ATOM__String);
+ osc_urid->ATOM_Literal = map->map(map->handle, LV2_ATOM__Literal);
+ osc_urid->ATOM_Float = map->map(map->handle, LV2_ATOM__Float);
+ osc_urid->ATOM_Double = map->map(map->handle, LV2_ATOM__Double);
+ osc_urid->ATOM_URID = map->map(map->handle, LV2_ATOM__URID);
+ osc_urid->ATOM_Bool = map->map(map->handle, LV2_ATOM__Bool);
+ osc_urid->ATOM_Tuple = map->map(map->handle, LV2_ATOM__Tuple);
+ osc_urid->ATOM_Object = map->map(map->handle, LV2_ATOM__Object);
+ osc_urid->ATOM_Chunk = map->map(map->handle, LV2_ATOM__Chunk);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_H
diff --git a/osc.lv2/osc.lv2/reader.h b/osc.lv2/osc.lv2/reader.h
new file mode 100644
index 0000000..9dda227
--- /dev/null
+++ b/osc.lv2/osc.lv2/reader.h
@@ -0,0 +1,570 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_READER_H
+#define LV2_OSC_READER_H
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/endian.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct _LV2_OSC_Reader LV2_OSC_Reader;
+typedef struct _LV2_OSC_Item LV2_OSC_Item;
+typedef struct _LV2_OSC_Arg LV2_OSC_Arg;
+
+struct _LV2_OSC_Reader {
+ const uint8_t *buf;
+ const uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Item {
+ int32_t size;
+ const uint8_t *body;
+
+ uint64_t timetag;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Arg {
+ const char *type;
+ int32_t size;
+ union {
+ int32_t i;
+ float f;
+ const char *s;
+ const uint8_t *b;
+
+ int64_t h;
+ double d;
+ uint64_t t;
+
+ const uint8_t *m;
+ const char *S;
+ char c;
+ struct {
+ uint8_t R;
+ uint8_t G;
+ uint8_t B;
+ uint8_t A;
+ }; // anonymous RGBA struct
+ };
+
+ const char *path;
+ const uint8_t *end;
+};
+
+static inline void
+lv2_osc_reader_initialize(LV2_OSC_Reader *reader, const uint8_t *buf, size_t size)
+{
+ reader->buf = buf;
+ reader->ptr = buf;
+ reader->end = buf + size;
+}
+
+static inline bool
+lv2_osc_reader_overflow(LV2_OSC_Reader *reader, size_t size)
+{
+ return reader->ptr + size > reader->end;
+}
+
+static inline bool
+lv2_osc_reader_be32toh(LV2_OSC_Reader *reader, union swap32_t *s32)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ s32->u = *(const uint32_t *)reader->ptr;
+ s32->u = be32toh(s32->u);
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_be64toh(LV2_OSC_Reader *reader, union swap64_t *s64)
+{
+ if(lv2_osc_reader_overflow(reader, 8))
+ return false;
+
+ s64->u = *(const uint64_t *)reader->ptr;
+ s64->u = be64toh(s64->u);
+ reader->ptr += 8;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_int32(LV2_OSC_Reader *reader, int32_t *i)
+{
+ union swap32_t s32;
+ if(!lv2_osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *i = s32.i;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_float(LV2_OSC_Reader *reader, float *f)
+{
+ union swap32_t s32;
+ if(!lv2_osc_reader_be32toh(reader, &s32))
+ return false;
+
+ *f = s32.f;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_int64(LV2_OSC_Reader *reader, int64_t *h)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *h = s64.h;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_timetag(LV2_OSC_Reader *reader, uint64_t *t)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *t = s64.u;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_double(LV2_OSC_Reader *reader, double *d)
+{
+ union swap64_t s64;
+ if(!lv2_osc_reader_be64toh(reader, &s64))
+ return false;
+
+ *d = s64.d;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_string(LV2_OSC_Reader *reader, const char **s)
+{
+ const char *str = (const char *)reader->ptr;
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(str) + 1);
+ if(lv2_osc_reader_overflow(reader, padded ))
+ return false;
+
+ *s = str;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_symbol(LV2_OSC_Reader *reader, const char **S)
+{
+ return lv2_osc_reader_get_string(reader, S);
+}
+
+static inline bool
+lv2_osc_reader_get_midi(LV2_OSC_Reader *reader, const uint8_t **m)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ *m = reader->ptr;
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_blob(LV2_OSC_Reader *reader, int32_t *len, const uint8_t **body)
+{
+ if(!lv2_osc_reader_get_int32(reader, len))
+ return false;
+
+ const size_t padded = LV2_OSC_PADDED_SIZE(*len);
+ if(lv2_osc_reader_overflow(reader, padded))
+ return false;
+
+ *body = reader->ptr;
+ reader->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_rgba(LV2_OSC_Reader *reader, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ if(lv2_osc_reader_overflow(reader, 4))
+ return false;
+
+ *r = reader->ptr[0];
+ *g = reader->ptr[1];
+ *b = reader->ptr[2];
+ *a = reader->ptr[3];
+ reader->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_get_char(LV2_OSC_Reader *reader, char *c)
+{
+ int32_t i;
+ if(!lv2_osc_reader_get_int32(reader, &i))
+ return false;
+
+ *c = i;
+
+ return true;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_raw(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ if(!lv2_osc_reader_get_int32(reader, &itm->size))
+ return NULL;
+
+ if(lv2_osc_reader_overflow(reader, itm->size))
+ return NULL;
+
+ itm->body = reader->ptr;
+
+ return itm;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_begin(LV2_OSC_Reader *reader, LV2_OSC_Item *itm, size_t len)
+{
+ if(lv2_osc_reader_overflow(reader, len))
+ return NULL;
+
+ itm->end = reader->ptr + len;
+
+ if(lv2_osc_reader_overflow(reader, 16))
+ return NULL;
+
+ if(strncmp((const char *)reader->ptr, "#bundle", 8))
+ return NULL;
+ reader->ptr += 8;
+
+ if(!lv2_osc_reader_get_timetag(reader, &itm->timetag))
+ return NULL;
+
+ return lv2_osc_reader_item_raw(reader, itm);
+}
+
+static inline bool
+lv2_osc_reader_item_is_end(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ return reader->ptr > itm->end;
+}
+
+static inline LV2_OSC_Item *
+lv2_osc_reader_item_next(LV2_OSC_Reader *reader, LV2_OSC_Item *itm)
+{
+ reader->ptr += itm->size;
+
+ return lv2_osc_reader_item_raw(reader, itm);
+}
+
+#define OSC_READER_BUNDLE_BEGIN(reader, len) \
+ lv2_osc_reader_item_begin( \
+ (reader), \
+ &(LV2_OSC_Item){ .size = 0, .body = NULL, .timetag = 1ULL, .end = NULL }, \
+ len)
+
+#define OSC_READER_BUNDLE_ITERATE(reader, itm) \
+ for(itm = itm; \
+ itm && !lv2_osc_reader_item_is_end((reader), (itm)); \
+ itm = lv2_osc_reader_item_next((reader), (itm)))
+
+#define OSC_READER_BUNDLE_FOREACH(reader, itm, len) \
+ for(LV2_OSC_Item *(itm) = OSC_READER_BUNDLE_BEGIN((reader), (len)); \
+ itm && !lv2_osc_reader_item_is_end((reader), (itm)); \
+ itm = lv2_osc_reader_item_next((reader), (itm)))
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_raw(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ switch( (LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ {
+ if(!lv2_osc_reader_get_int32(reader, &arg->i))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_FLOAT:
+ {
+ if(!lv2_osc_reader_get_float(reader, &arg->f))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_STRING:
+ {
+ if(!lv2_osc_reader_get_string(reader, &arg->s))
+ return NULL;
+ arg->size = strlen(arg->s) + 1;
+
+ break;
+ }
+ case LV2_OSC_BLOB:
+ {
+ if(!lv2_osc_reader_get_blob(reader, &arg->size, &arg->b))
+ return NULL;
+ //arg->size = arg->size;
+
+ break;
+ }
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ {
+ if(!lv2_osc_reader_get_int64(reader, &arg->h))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_DOUBLE:
+ {
+ if(!lv2_osc_reader_get_double(reader, &arg->d))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+ case LV2_OSC_TIMETAG:
+ {
+ if(!lv2_osc_reader_get_timetag(reader, &arg->t))
+ return NULL;
+ arg->size = 8;
+
+ break;
+ }
+
+ case LV2_OSC_MIDI:
+ {
+ if(!lv2_osc_reader_get_midi(reader, &arg->m))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_SYMBOL:
+ {
+ if(!lv2_osc_reader_get_symbol(reader, &arg->S))
+ return NULL;
+ arg->size = strlen(arg->S) + 1;
+
+ break;
+ }
+ case LV2_OSC_CHAR:
+ {
+ if(!lv2_osc_reader_get_char(reader, &arg->c))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ case LV2_OSC_RGBA:
+ {
+ if(!lv2_osc_reader_get_rgba(reader, &arg->R, &arg->G, &arg->B, &arg->A))
+ return NULL;
+ arg->size = 4;
+
+ break;
+ }
+ }
+
+ return arg;
+}
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_begin(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg, size_t len)
+{
+ if(lv2_osc_reader_overflow(reader, len))
+ return NULL;
+
+ arg->end = reader->ptr + len;
+
+ if(!lv2_osc_reader_get_string(reader, &arg->path)) //TODO check for validity
+ return NULL;
+
+ if(!lv2_osc_reader_get_string(reader, &arg->type)) //TODO check for validity
+ return NULL;
+
+ if(*arg->type != ',')
+ return NULL;
+
+ arg->type++; // skip ','
+
+ return lv2_osc_reader_arg_raw(reader, arg);
+}
+
+static inline bool
+lv2_osc_reader_arg_is_end(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ return (*arg->type == '\0') || (reader->ptr > arg->end);
+}
+
+static inline LV2_OSC_Arg *
+lv2_osc_reader_arg_next(LV2_OSC_Reader *reader, LV2_OSC_Arg *arg)
+{
+ arg->type++;
+
+ return lv2_osc_reader_arg_raw(reader, arg);
+}
+
+#define OSC_READER_MESSAGE_BEGIN(reader, len) \
+ lv2_osc_reader_arg_begin( \
+ (reader), \
+ &(LV2_OSC_Arg){ .type = NULL, .size = 0, .path = NULL, .end = NULL }, \
+ len)
+
+#define OSC_READER_MESSAGE_ITERATE(reader, arg) \
+ for(arg = arg; \
+ arg && !lv2_osc_reader_arg_is_end((reader), (arg)); \
+ arg = lv2_osc_reader_arg_next((reader), (arg)))
+
+#define OSC_READER_MESSAGE_FOREACH(reader, arg, len) \
+ for(LV2_OSC_Arg *(arg) = OSC_READER_MESSAGE_BEGIN((reader), (len)); \
+ arg && !lv2_osc_reader_arg_is_end((reader), (arg)); \
+ arg = lv2_osc_reader_arg_next((reader), (arg)))
+
+static inline bool
+lv2_osc_reader_arg_varlist(LV2_OSC_Reader *reader, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ if(!lv2_osc_reader_get_int32(reader, va_arg(args, int32_t *)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!lv2_osc_reader_get_float(reader, va_arg(args, float *)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!lv2_osc_reader_get_string(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!lv2_osc_reader_get_blob(reader, va_arg(args, int32_t *), va_arg(args, const uint8_t **)))
+ return false;
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!lv2_osc_reader_get_int64(reader, va_arg(args, int64_t *)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!lv2_osc_reader_get_double(reader, va_arg(args, double *)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!lv2_osc_reader_get_timetag(reader, va_arg(args, uint64_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!lv2_osc_reader_get_midi(reader, va_arg(args, const uint8_t **)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!lv2_osc_reader_get_symbol(reader, va_arg(args, const char **)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!lv2_osc_reader_get_char(reader, va_arg(args, char *)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!lv2_osc_reader_get_rgba(reader, va_arg(args, uint8_t *), va_arg(args, uint8_t *),
+ va_arg(args, uint8_t *), va_arg(args, uint8_t *)))
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_reader_arg_vararg(LV2_OSC_Reader *reader, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_reader_arg_varlist(reader, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_reader_is_bundle(LV2_OSC_Reader *reader)
+{
+ return strncmp((const char *)reader->ptr, "#bundle", 8) == 0;
+}
+
+static inline bool
+lv2_osc_reader_is_message(LV2_OSC_Reader *reader)
+{
+ return reader->ptr[0] == '/'; //FIXME check path
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_READER_H
diff --git a/osc.lv2/osc.lv2/util.h b/osc.lv2/osc.lv2/util.h
new file mode 100644
index 0000000..b9d3746
--- /dev/null
+++ b/osc.lv2/osc.lv2/util.h
@@ -0,0 +1,481 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_UTIL_H
+#define LV2_OSC_UTIL_H
+
+#include <assert.h>
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdio.h>
+
+#include <osc.lv2/osc.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
+#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+
+typedef void (*LV2_OSC_Method)(const char *path,
+ const LV2_Atom_Tuple *arguments, void *data);
+
+// characters not allowed in OSC path string
+static const char invalid_path_chars [] = {
+ ' ', '#',
+ '\0'
+};
+
+// allowed characters in OSC format string
+static const char valid_format_chars [] = {
+ LV2_OSC_INT32, LV2_OSC_FLOAT, LV2_OSC_STRING, LV2_OSC_BLOB,
+ LV2_OSC_TRUE, LV2_OSC_FALSE, LV2_OSC_NIL, LV2_OSC_IMPULSE,
+ LV2_OSC_INT64, LV2_OSC_DOUBLE, LV2_OSC_TIMETAG,
+ LV2_OSC_SYMBOL, LV2_OSC_MIDI,
+ '\0'
+};
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_check_path(const char *path)
+{
+ assert(path);
+
+ if(path[0] != '/')
+ return false;
+
+ for(const char *ptr=path+1; *ptr!='\0'; ptr++)
+ if( (isprint(*ptr) == 0) || (strchr(invalid_path_chars, *ptr) != NULL) )
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_check_fmt(const char *format, int offset)
+{
+ assert(format);
+
+ if(offset && (format[0] != ',') )
+ return false;
+
+ for(const char *ptr=format+offset; *ptr!='\0'; ptr++)
+ if(strchr(valid_format_chars, *ptr) == NULL)
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline uint64_t
+lv2_osc_timetag_parse(const LV2_OSC_Timetag *timetag)
+{
+ return ((uint64_t)timetag->integral << 32) | timetag->fraction;
+}
+
+/**
+ TODO
+*/
+static inline LV2_OSC_Timetag *
+lv2_osc_timetag_create(LV2_OSC_Timetag *timetag, uint64_t tt)
+{
+ timetag->integral = tt >> 32;
+ timetag->fraction = tt & 0xffffffff;
+
+ return timetag;
+}
+
+#define LV2_OSC_TIMETAG_CREATE(tt) \
+ lv2_osc_timetag_create(&(LV2_OSC_Timetag){.integral = 0, .fraction = 0}, (tt))
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_packet_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Packet;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Bundle;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_message_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return type == osc_urid->OSC_Message;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_is_message_or_bundle_type(LV2_OSC_URID *osc_urid, LV2_URID type)
+{
+ return lv2_osc_is_message_type(osc_urid, type)
+ || lv2_osc_is_bundle_type(osc_urid, type);
+}
+
+static inline LV2_OSC_Type
+lv2_osc_argument_type(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ if(atom->type == osc_urid->ATOM_Int)
+ return LV2_OSC_INT32;
+ else if(atom->type == osc_urid->ATOM_Float)
+ return LV2_OSC_FLOAT;
+ else if(atom->type == osc_urid->ATOM_String)
+ return LV2_OSC_STRING;
+ else if(atom->type == osc_urid->ATOM_Chunk)
+ return LV2_OSC_BLOB;
+
+ else if(atom->type == osc_urid->ATOM_Long)
+ return LV2_OSC_INT64;
+ else if(atom->type == osc_urid->ATOM_Double)
+ return LV2_OSC_DOUBLE;
+ else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
+ return LV2_OSC_TIMETAG;
+
+ else if(atom->type == osc_urid->ATOM_Bool)
+ {
+ if(((const LV2_Atom_Bool *)atom)->body)
+ return LV2_OSC_TRUE;
+ else
+ return LV2_OSC_FALSE;
+ }
+ else if(atom->type == osc_urid->ATOM_Literal)
+ {
+ const LV2_Atom_Literal *lit = (const LV2_Atom_Literal *)atom;
+ if(lit->body.datatype == osc_urid->OSC_Nil)
+ return LV2_OSC_NIL;
+ else if(lit->body.datatype == osc_urid->OSC_Impulse)
+ return LV2_OSC_IMPULSE;
+ else if(lit->body.datatype == osc_urid->OSC_Char)
+ return LV2_OSC_CHAR;
+ else if(lit->body.datatype == osc_urid->OSC_RGBA)
+ return LV2_OSC_RGBA;
+ }
+
+ else if(atom->type == osc_urid->ATOM_URID)
+ return LV2_OSC_SYMBOL;
+ else if(atom->type == osc_urid->MIDI_MidiEvent)
+ return LV2_OSC_MIDI;
+
+ return '\0';
+}
+
+static inline const LV2_Atom *
+lv2_osc_int32_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, int32_t *i)
+{
+ assert(i);
+ *i = ((const LV2_Atom_Int *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_float_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, float *f)
+{
+ assert(f);
+ *f = ((const LV2_Atom_Float *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_string_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, const char **s)
+{
+ assert(s);
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_blob_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, uint32_t *size,
+ const uint8_t **b)
+{
+ assert(size && b);
+ *size = atom->size;
+ *b = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_int64_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, int64_t *h)
+{
+ assert(h);
+ *h = ((const LV2_Atom_Long *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_double_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, double *d)
+{
+ assert(d);
+ *d = ((const LV2_Atom_Double *)atom)->body;
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_timetag_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom,
+ LV2_OSC_Timetag *timetag)
+{
+ assert(timetag);
+
+ const LV2_Atom_Long *integral = NULL;
+ const LV2_Atom_Long *fraction = NULL;
+
+ lv2_atom_object_get((const LV2_Atom_Object *)atom,
+ osc_urid->OSC_timetagIntegral, &integral,
+ osc_urid->OSC_timetagFraction, &fraction,
+ 0);
+
+ if( integral && (integral->atom.type == osc_urid->ATOM_Long)
+ && fraction && (fraction->atom.type == osc_urid->ATOM_Long) )
+ {
+ timetag->integral = integral->body;
+ timetag->fraction = fraction->body;
+ }
+ else
+ {
+ // set to immediate
+ timetag->integral = 0;
+ timetag->fraction = 1;
+ }
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_true_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_false_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_nil_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_impulse_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom)
+{
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_symbol_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, const char **s)
+{
+ assert(s);
+ *s = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_midi_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, uint32_t *size,
+ const uint8_t **m)
+{
+ assert(size && m);
+ *size = atom->size;
+ *m = LV2_ATOM_BODY_CONST(atom);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_char_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom, char *c)
+{
+ assert(c);
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ *c = str[0];
+
+ return lv2_atom_tuple_next(atom);
+}
+
+static inline const LV2_Atom *
+lv2_osc_rgba_get(LV2_OSC_URID *osc_urid, const LV2_Atom *atom,
+ uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a)
+{
+ assert(r && g && b && a);
+ const char *str = LV2_ATOM_CONTENTS_CONST(LV2_Atom_Literal, atom);
+ sscanf(str, "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8, r, g, b, a);
+
+ return lv2_atom_tuple_next(atom);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_bundle_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
+{
+ assert(timetag && items);
+
+ *timetag = NULL;
+ *items = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ osc_urid->OSC_bundleTimetag, timetag,
+ osc_urid->OSC_bundleItems, items,
+ 0);
+
+ if(!*timetag || ((*timetag)->atom.type != osc_urid->ATOM_Object) || ((*timetag)->body.otype != osc_urid->OSC_Timetag))
+ return false;
+ if(!*items || ((*items)->atom.type != osc_urid->ATOM_Tuple))
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_bundle_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ const LV2_Atom_Object **timetag, const LV2_Atom_Tuple **items)
+{
+ return lv2_osc_bundle_body_get(osc_urid, obj->atom.size, &obj->body,
+ timetag, items);
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_message_body_get(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
+{
+ assert(path && arguments);
+
+ *path = NULL;
+ *arguments = NULL;
+
+ lv2_atom_object_body_get(size, body,
+ osc_urid->OSC_messagePath, path,
+ osc_urid->OSC_messageArguments, arguments,
+ 0);
+
+ if(!*path || ((*path)->atom.type != osc_urid->ATOM_String))
+ return false;
+ // message without arguments is valid
+ if( *arguments && ((*arguments)->atom.type != osc_urid->ATOM_Tuple))
+ return false;
+
+ return true;
+}
+
+/**
+ TODO
+*/
+static inline bool
+lv2_osc_message_get(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ const LV2_Atom_String **path, const LV2_Atom_Tuple **arguments)
+{
+ return lv2_osc_message_body_get(osc_urid, obj->atom.size, &obj->body,
+ path, arguments);
+}
+
+static inline bool
+lv2_osc_body_unroll(LV2_OSC_URID *osc_urid, uint32_t size, const LV2_Atom_Object_Body *body,
+ LV2_OSC_Method method, void *data)
+{
+ if(body->otype == osc_urid->OSC_Bundle)
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+
+ if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
+ return false;
+
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
+
+ LV2_ATOM_TUPLE_FOREACH(items, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+
+ if(!lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data))
+ return false;
+ }
+
+ return true;
+ }
+ else if(body->otype == osc_urid->OSC_Message)
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *arguments = NULL;
+
+ if(!lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
+ return false;
+
+ if(method)
+ method(LV2_ATOM_BODY_CONST(path), arguments, data);
+
+ return true;
+ }
+
+ return false;
+}
+
+static inline bool
+lv2_osc_unroll(LV2_OSC_URID *osc_urid, const LV2_Atom_Object *obj,
+ LV2_OSC_Method method, void *data)
+{
+ return lv2_osc_body_unroll(osc_urid, obj->atom.size, &obj->body, method, data);
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_UTIL_H
diff --git a/osc.lv2/osc.lv2/writer.h b/osc.lv2/osc.lv2/writer.h
new file mode 100644
index 0000000..c081cad
--- /dev/null
+++ b/osc.lv2/osc.lv2/writer.h
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef LV2_OSC_WRITER_H
+#define LV2_OSC_WRITER_H
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/util.h>
+#include <osc.lv2/endian.h>
+
+#include <lv2/lv2plug.in/ns/ext/atom/util.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#undef LV2_ATOM_TUPLE_FOREACH // there is a bug in LV2 1.10.0
+#define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+
+typedef struct _LV2_OSC_Writer LV2_OSC_Writer;
+typedef struct _LV2_OSC_Writer_Frame LV2_OSC_Writer_Frame;
+
+struct _LV2_OSC_Writer {
+ uint8_t *buf;
+ uint8_t *ptr;
+ const uint8_t *end;
+};
+
+struct _LV2_OSC_Writer_Frame {
+ uint8_t *ref;
+};
+
+static inline void
+lv2_osc_writer_initialize(LV2_OSC_Writer *writer, uint8_t *buf, size_t size)
+{
+ writer->buf = buf;
+ writer->ptr = buf;
+ writer->end = buf + size;
+}
+
+static inline size_t
+lv2_osc_writer_get_size(LV2_OSC_Writer *writer)
+{
+ if(writer->ptr > writer->buf)
+ return writer->ptr - writer->buf;
+
+ return 0;
+}
+
+static inline uint8_t *
+lv2_osc_writer_finalize(LV2_OSC_Writer *writer, size_t *size)
+{
+ *size = lv2_osc_writer_get_size(writer);
+
+ if(*size)
+ return writer->buf;
+
+ return NULL;
+}
+
+static inline bool
+lv2_osc_writer_overflow(LV2_OSC_Writer *writer, size_t size)
+{
+ return writer->ptr + size >= writer->end;
+}
+
+static inline bool
+lv2_osc_writer_htobe32(LV2_OSC_Writer *writer, union swap32_t *s32)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ s32->u = htobe32(s32->u);
+ *(uint32_t *)writer->ptr = s32->u;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_htobe64(LV2_OSC_Writer *writer, union swap64_t *s64)
+{
+ if(lv2_osc_writer_overflow(writer, 8))
+ return false;
+
+ s64->u = htobe64(s64->u);
+ *(uint64_t *)writer->ptr = s64->u;
+ writer->ptr += 8;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_int32(LV2_OSC_Writer *writer, int32_t i)
+{
+ return lv2_osc_writer_htobe32(writer, &(union swap32_t){ .i = i });
+}
+
+static inline bool
+lv2_osc_writer_add_float(LV2_OSC_Writer *writer, float f)
+{
+ return lv2_osc_writer_htobe32(writer, &(union swap32_t){ .f = f });
+}
+
+static inline bool
+lv2_osc_writer_add_string(LV2_OSC_Writer *writer, const char *s)
+{
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(s) + 1);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ strncpy((char *)writer->ptr, s, padded);
+ writer->ptr += padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_symbol(LV2_OSC_Writer *writer, const char *S)
+{
+ return lv2_osc_writer_add_string(writer, S);
+}
+
+static inline bool
+lv2_osc_writer_add_int64(LV2_OSC_Writer *writer, int64_t h)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .h = h });
+}
+
+static inline bool
+lv2_osc_writer_add_double(LV2_OSC_Writer *writer, double d)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .d = d });
+}
+
+static inline bool
+lv2_osc_writer_add_timetag(LV2_OSC_Writer *writer, uint64_t u)
+{
+ return lv2_osc_writer_htobe64(writer, &(union swap64_t){ .u = u });
+}
+
+static inline bool
+lv2_osc_writer_add_blob_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **body)
+{
+ const size_t len_padded = LV2_OSC_PADDED_SIZE(len);
+ const size_t size = 4 + len_padded;
+ if(lv2_osc_writer_overflow(writer, size))
+ return false;
+
+ if(!lv2_osc_writer_add_int32(writer, len))
+ return false;
+
+ *body = writer->ptr;
+ memset(&writer->ptr[len], 0x0, len_padded - len);
+ writer->ptr += len_padded;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_blob(LV2_OSC_Writer *writer, int32_t len, const uint8_t *body)
+{
+ uint8_t *dst;
+ if(!lv2_osc_writer_add_blob_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, body, len);
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_midi_inline(LV2_OSC_Writer *writer, int32_t len, uint8_t **m)
+{
+ if( (len > 4) || lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ *m = writer->ptr;
+ memset(&writer->ptr[len], 0x0, 4 - len);
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_midi(LV2_OSC_Writer *writer, int32_t len, const uint8_t *m)
+{
+ uint8_t *dst;
+ if(!lv2_osc_writer_add_midi_inline(writer, len, &dst))
+ return false;
+
+ memcpy(dst, m, len);
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_rgba(LV2_OSC_Writer *writer, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ writer->ptr[0] = r;
+ writer->ptr[1] = g;
+ writer->ptr[2] = b;
+ writer->ptr[3] = a;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_char(LV2_OSC_Writer *writer, char c)
+{
+ return lv2_osc_writer_add_int32(writer, (int32_t)c);
+}
+
+static inline bool
+lv2_osc_writer_push_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame, uint64_t t)
+{
+ if(lv2_osc_writer_overflow(writer, 16))
+ return false;
+
+ frame->ref = writer->ptr;
+
+ strncpy((char *)writer->ptr, "#bundle", 8);
+ writer->ptr += 8;
+
+ return lv2_osc_writer_add_timetag(writer, t);
+}
+
+static inline bool
+lv2_osc_writer_pop_bundle(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ union swap32_t s32 = { .i = writer->ptr - frame->ref - 16};
+
+ if(s32.i <= 0)
+ {
+ writer->ptr = frame->ref;
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_push_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ if(lv2_osc_writer_overflow(writer, 4))
+ return false;
+
+ frame->ref = writer->ptr;
+ writer->ptr += 4;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_pop_item(LV2_OSC_Writer *writer, LV2_OSC_Writer_Frame *frame)
+{
+ union swap32_t s32 = { .i = writer->ptr - frame->ref - 4};
+
+ if(s32.i <= 0)
+ {
+ writer->ptr = frame->ref;
+ return false;
+ }
+
+ s32.u = htobe32(s32.u);
+ *(uint32_t *)frame->ref = s32.u;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_add_path(LV2_OSC_Writer *writer, const char *path)
+{
+ return lv2_osc_writer_add_string(writer, path);
+}
+
+static inline bool
+lv2_osc_writer_add_format(LV2_OSC_Writer *writer, const char *fmt)
+{
+ const size_t padded = LV2_OSC_PADDED_SIZE(strlen(fmt) + 2);
+ if(lv2_osc_writer_overflow(writer, padded))
+ return false;
+
+ *writer->ptr++ = ',';
+ strncpy((char *)writer->ptr, fmt, padded - 1);
+ writer->ptr += padded - 1;
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_arg_varlist(LV2_OSC_Writer *writer, const char *fmt, va_list args)
+{
+ for(const char *type = fmt; *type; type++)
+ {
+ switch( (LV2_OSC_Type)*type)
+ {
+ case LV2_OSC_INT32:
+ if(!lv2_osc_writer_add_int32(writer, va_arg(args, int32_t)))
+ return false;
+ break;
+ case LV2_OSC_FLOAT:
+ if(!lv2_osc_writer_add_float(writer, (float)va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_STRING:
+ if(!lv2_osc_writer_add_string(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_BLOB:
+ if(!lv2_osc_writer_add_blob(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ return false;
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_INT64:
+ if(!lv2_osc_writer_add_int64(writer, va_arg(args, int64_t)))
+ return false;
+ break;
+ case LV2_OSC_DOUBLE:
+ if(!lv2_osc_writer_add_double(writer, va_arg(args, double)))
+ return false;
+ break;
+ case LV2_OSC_TIMETAG:
+ if(!lv2_osc_writer_add_timetag(writer, va_arg(args, uint64_t)))
+ return false;
+ break;
+
+ case LV2_OSC_MIDI:
+ if(!lv2_osc_writer_add_midi(writer, va_arg(args, int32_t), va_arg(args, const uint8_t *)))
+ return false;
+ break;
+ case LV2_OSC_SYMBOL:
+ if(!lv2_osc_writer_add_symbol(writer, va_arg(args, const char *)))
+ return false;
+ break;
+ case LV2_OSC_CHAR:
+ if(!lv2_osc_writer_add_char(writer, va_arg(args, int)))
+ return false;
+ break;
+ case LV2_OSC_RGBA:
+ if(!lv2_osc_writer_add_rgba(writer, va_arg(args, unsigned), va_arg(args, unsigned),
+ va_arg(args, unsigned), va_arg(args, unsigned)))
+ return false;
+ break;
+ }
+ }
+
+ return true;
+}
+
+static inline bool
+lv2_osc_writer_arg_vararg(LV2_OSC_Writer *writer, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_writer_arg_varlist(writer, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_writer_message_varlist(LV2_OSC_Writer *writer, const char *path, const char *fmt, va_list args)
+{
+ if(!lv2_osc_writer_add_path(writer, path))
+ return false;
+
+ if(!lv2_osc_writer_add_format(writer, fmt))
+ return false;
+
+ return lv2_osc_writer_arg_varlist(writer, fmt, args);
+}
+
+static inline bool
+lv2_osc_writer_message_vararg(LV2_OSC_Writer *writer, const char *path, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ const bool res = lv2_osc_writer_message_varlist(writer, path, fmt, args);
+
+ va_end(args);
+
+ return res;
+}
+
+static inline bool
+lv2_osc_writer_packet(LV2_OSC_Writer *writer, LV2_OSC_URID *osc_urid,
+ LV2_URID_Unmap *unmap, uint32_t size, const LV2_Atom_Object_Body *body)
+{
+ if(body->otype == osc_urid->OSC_Bundle)
+ {
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+
+ if(!lv2_osc_bundle_body_get(osc_urid, size, body, &timetag, &items))
+ return false;
+
+ LV2_OSC_Timetag tt;
+ LV2_OSC_Writer_Frame bndl;
+
+ lv2_osc_timetag_get(osc_urid, &timetag->atom, &tt);
+ if(!lv2_osc_writer_push_bundle(writer, &bndl, lv2_osc_timetag_parse(&tt)))
+ return false;
+
+ LV2_ATOM_TUPLE_FOREACH(items, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+ LV2_OSC_Writer_Frame itm;
+
+ if( !lv2_osc_writer_push_item(writer, &itm)
+ || !lv2_osc_writer_packet(writer, osc_urid, unmap, obj->atom.size, &obj->body)
+ || !lv2_osc_writer_pop_item(writer, &itm) )
+ {
+ return false;
+ }
+ }
+
+ return lv2_osc_writer_pop_bundle(writer, &bndl);
+ }
+ else if(body->otype == osc_urid->OSC_Message)
+ {
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *arguments = NULL;
+
+ if(lv2_osc_message_body_get(osc_urid, size, body, &path, &arguments))
+ {
+ if(!lv2_osc_writer_add_path(writer, LV2_ATOM_BODY_CONST(path)))
+ return false;
+
+ char fmt [128]; //TODO how big?
+ char *ptr = fmt;
+ LV2_ATOM_TUPLE_FOREACH(arguments, atom)
+ {
+ *ptr++ = lv2_osc_argument_type(osc_urid, atom);
+ }
+ *ptr = '\0';
+ if(!lv2_osc_writer_add_format(writer, fmt))
+ return false;
+
+ LV2_ATOM_TUPLE_FOREACH(arguments, atom)
+ {
+ const LV2_Atom_Object *obj= (const LV2_Atom_Object *)atom;
+
+ if(atom->type == osc_urid->ATOM_Int)
+ {
+ if(!lv2_osc_writer_add_int32(writer, ((const LV2_Atom_Int *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Float)
+ {
+ if(!lv2_osc_writer_add_float(writer, ((const LV2_Atom_Float *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_String)
+ {
+ if(!lv2_osc_writer_add_string(writer, LV2_ATOM_BODY_CONST(atom)))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Chunk)
+ {
+ if(!lv2_osc_writer_add_blob(writer, atom->size, LV2_ATOM_BODY_CONST(atom)))
+ return false;
+ }
+
+ else if(atom->type == osc_urid->ATOM_Long)
+ {
+ if(!lv2_osc_writer_add_int64(writer, ((const LV2_Atom_Long *)atom)->body))
+ return false;
+ }
+ else if(atom->type == osc_urid->ATOM_Double)
+ {
+ if(!lv2_osc_writer_add_double(writer, ((const LV2_Atom_Double *)atom)->body))
+ return false;
+ }
+ else if( (atom->type == osc_urid->ATOM_Object) && (obj->body.otype == osc_urid->OSC_Timetag) )
+ {
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(osc_urid, &obj->atom, &tt);
+ if(!lv2_osc_writer_add_timetag(writer, lv2_osc_timetag_parse(&tt)))
+ return false;
+ }
+
+ // there is nothing to do for: true, false, nil, impulse
+
+ else if(atom->type == osc_urid->ATOM_URID)
+ {
+ const char *symbol = unmap->unmap(unmap->handle, ((const LV2_Atom_URID *)atom)->body);
+ if(!symbol || !lv2_osc_writer_add_symbol(writer, symbol))
+ return false;
+ }
+ else if(atom->type == osc_urid->MIDI_MidiEvent)
+ {
+ uint8_t *m = NULL;
+ if(!lv2_osc_writer_add_midi_inline(writer, atom->size + 1, &m))
+ return false;
+ m[0] = 0x0; // port
+ memcpy(&m[1], LV2_ATOM_BODY_CONST(atom), atom->size);
+ }
+ else if(atom->type == osc_urid->OSC_Char)
+ {
+ const char c = *(const char *)LV2_ATOM_BODY_CONST(atom);
+ if(!lv2_osc_writer_add_char(writer, c))
+ return false;
+ }
+ else if(atom->type == osc_urid->OSC_RGBA)
+ {
+ const uint8_t *rgba = LV2_ATOM_BODY_CONST(atom);
+ if(!lv2_osc_writer_add_rgba(writer, rgba[0], rgba[1], rgba[2], rgba[3]))
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // LV2_OSC_WRITER_H
diff --git a/osc.lv2/osc.ttl b/osc.lv2/osc.ttl
new file mode 100644
index 0000000..db4a048
--- /dev/null
+++ b/osc.lv2/osc.ttl
@@ -0,0 +1,42 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+
+<http://open-music-kontrollers.ch/lv2/osc>
+ a owl:Ontology ;
+ rdfs:seeAlso <lv2_osc.h> ,
+ <lv2-osc.doap.ttl> ;
+ lv2:documentation """
+ <p>This specification defines event data types for OSC bundles and message.
+ To signal support for OSC events on an atom:AtomPort with an atom:bufferType
+ of atom:Sequence, plugin authors should add atom:supports osc:Event to
+ the plugin specification.</p>
+ """ .
+
+osc:schedule
+ a lv2:Feature .
+
+osc:Event
+ a rdfs:Class ,
+ rdfs:Datatype ;
+ rdfs:subClassOf atom:Atom ;
+ owl:onDatatype xsd:hexBinary ;
+ rdfs:label "OSC Event (Bundle or Message)" .
diff --git a/osc.lv2/osc_test.c b/osc.lv2/osc_test.c
new file mode 100644
index 0000000..1280fc0
--- /dev/null
+++ b/osc.lv2/osc_test.c
@@ -0,0 +1,423 @@
+#include <assert.h>
+#include <string.h>
+#include <stdio.h>
+
+#include <osc.lv2/osc.h>
+#include <osc.lv2/reader.h>
+#include <osc.lv2/writer.h>
+#include <osc.lv2/forge.h>
+
+#define BUF_SIZE 8192
+#define MAX_URIDS 512
+
+typedef void (*test_t)(LV2_OSC_Writer *writer);
+typedef struct _urid_t urid_t;
+typedef struct _handle_t handle_t;
+
+struct _urid_t {
+ LV2_URID urid;
+ char *uri;
+};
+
+struct _handle_t {
+ urid_t urids [MAX_URIDS];
+ LV2_URID urid;
+};
+
+static handle_t __handle;
+static uint8_t buf0 [BUF_SIZE];
+static uint8_t buf1 [BUF_SIZE];
+static uint8_t buf2 [BUF_SIZE];
+static const LV2_Atom_Object *obj2= (const LV2_Atom_Object *)buf2;
+
+const uint8_t raw_0 [] = {
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_1 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'i', 'f', 's',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x59, 0x99, 0x9a,
+ 'w', 'o', 'r', 'l',
+ 'd', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_2 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'h', 'd', 'S',
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0xc,
+ 0x40, 0x0b, 0x33, 0x33,
+ 0x33, 0x33, 0x33, 0x33,
+ 'h', 't', 't', 'p',
+ ':', '/', '/', 'e',
+ 'x', 'a', 'm', 'p',
+ 'l', 'e', '.', 'c',
+ 'o', 'm', 0x0, 0x0
+};
+
+const uint8_t raw_3 [] = {
+ '/', 'p', 'i', 'n',
+ 'g', 0x0, 0x0, 0x0,
+ ',', 'T', 'F', 'N',
+ 'I', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_4 [] = {
+ '/', 'm', 'i', 'd',
+ 'i', 0x0, 0x0, 0x0,
+ ',', 'm', 0x0, 0x0,
+ 0x0, 0x90, 24, 0x7f
+};
+
+const uint8_t raw_5 [] = {
+ '/', 'b', 'l', 'o',
+ 'b', 0x0, 0x0, 0x0,
+ ',', 'b', 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x6,
+ 0x1, 0x2, 0x3, 0x4,
+ 0x5, 0x6, 0x0, 0x0
+};
+
+const uint8_t raw_6 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+const uint8_t raw_7 [] = {
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x1c,
+ '#', 'b', 'u', 'n',
+ 'd', 'l', 'e', 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x1,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0,
+
+ 0x0, 0x0, 0x0, 0x8,
+ '/', 0x0, 0x0, 0x0,
+ ',', 0x0, 0x0, 0x0
+};
+
+static LV2_URID
+_map(LV2_URID_Map_Handle instance, const char *uri)
+{
+ handle_t *handle = instance;
+
+ urid_t *itm;
+ for(itm=handle->urids; itm->urid; itm++)
+ {
+ if(!strcmp(itm->uri, uri))
+ return itm->urid;
+ }
+
+ assert(handle->urid + 1 < MAX_URIDS);
+
+ // create new
+ itm->urid = ++handle->urid;
+ itm->uri = strdup(uri);
+
+ return itm->urid;
+}
+
+static const char *
+_unmap(LV2_URID_Unmap_Handle instance, LV2_URID urid)
+{
+ handle_t *handle = instance;
+
+ urid_t *itm;
+ for(itm=handle->urids; itm->urid; itm++)
+ {
+ if(itm->urid == urid)
+ return itm->uri;
+ }
+
+ // not found
+ return NULL;
+}
+
+static LV2_URID_Map map = {
+ .handle = &__handle,
+ .map = _map
+};
+
+static LV2_URID_Unmap unmap = {
+ .handle = &__handle,
+ .unmap = _unmap
+};
+
+static void
+_dump(const uint8_t *src, const uint8_t *dst, size_t size)
+{
+ for(size_t i = 0; i < size; i++)
+ printf("%zu %02x %02x\n", i, src[i], dst[i]);
+ printf("\n");
+}
+
+static void
+_clone(LV2_OSC_Reader *reader, LV2_OSC_Writer *writer, size_t size)
+{
+ if(lv2_osc_reader_is_bundle(reader))
+ {
+ LV2_OSC_Item *itm = OSC_READER_BUNDLE_BEGIN(reader, size);
+ assert(itm);
+
+ LV2_OSC_Writer_Frame frame_bndl;
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl, itm->timetag));
+
+ OSC_READER_BUNDLE_ITERATE(reader, itm)
+ {
+ LV2_OSC_Reader reader2;
+ lv2_osc_reader_initialize(&reader2, itm->body, itm->size);
+
+ LV2_OSC_Writer_Frame frame_itm;
+ assert(lv2_osc_writer_push_item(writer, &frame_itm));
+ _clone(&reader2, writer, itm->size);
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm));
+ }
+
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl));
+ }
+ else if(lv2_osc_reader_is_message(reader))
+ {
+ LV2_OSC_Arg *arg = OSC_READER_MESSAGE_BEGIN(reader, size);
+ assert(arg);
+
+ assert(lv2_osc_writer_add_path(writer, arg->path));
+ assert(lv2_osc_writer_add_format(writer, arg->type));
+
+ OSC_READER_MESSAGE_ITERATE(reader, arg)
+ {
+ switch((LV2_OSC_Type)*arg->type)
+ {
+ case LV2_OSC_INT32:
+ assert(lv2_osc_writer_add_int32(writer, arg->i));
+ break;
+ case LV2_OSC_FLOAT:
+ assert(lv2_osc_writer_add_float(writer, arg->f));
+ break;
+ case LV2_OSC_STRING:
+ assert(lv2_osc_writer_add_string(writer, arg->s));
+ break;
+ case LV2_OSC_BLOB:
+ assert(lv2_osc_writer_add_blob(writer, arg->size, arg->b));
+ break;
+
+ case LV2_OSC_INT64:
+ assert(lv2_osc_writer_add_int64(writer, arg->h));
+ break;
+ case LV2_OSC_DOUBLE:
+ assert(lv2_osc_writer_add_double(writer, arg->d));
+ break;
+ case LV2_OSC_TIMETAG:
+ assert(lv2_osc_writer_add_timetag(writer, arg->t));
+ break;
+
+ case LV2_OSC_TRUE:
+ case LV2_OSC_FALSE:
+ case LV2_OSC_NIL:
+ case LV2_OSC_IMPULSE:
+ break;
+
+ case LV2_OSC_MIDI:
+ assert(lv2_osc_writer_add_midi(writer, arg->size, arg->m));
+ break;
+ case LV2_OSC_SYMBOL:
+ assert(lv2_osc_writer_add_symbol(writer, arg->S));
+ break;
+ case LV2_OSC_CHAR:
+ assert(lv2_osc_writer_add_char(writer, arg->c));
+ break;
+ case LV2_OSC_RGBA:
+ assert(lv2_osc_writer_add_rgba(writer, arg->R, arg->G, arg->B, arg->A));
+ break;
+ }
+ }
+ }
+}
+
+static void
+_test_a(LV2_OSC_Writer *writer, const uint8_t *raw, size_t size)
+{
+ LV2_OSC_URID osc_urid;
+ lv2_osc_urid_init(&osc_urid, &map);
+
+ // check writer against raw bytes
+ size_t len;
+ assert(lv2_osc_writer_finalize(writer, &len) == buf0);
+ assert(len == size);
+ assert(memcmp(raw, buf0, size) == 0);
+
+ // check reader & writer
+ LV2_OSC_Reader reader;
+ lv2_osc_reader_initialize(&reader, buf0, size);
+ lv2_osc_writer_initialize(writer, buf1, BUF_SIZE);
+ _clone(&reader, writer, size);
+
+ // check cloned against raw bytes
+ assert(lv2_osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+ assert(memcmp(raw, buf1, size) == 0);
+
+ // check forge
+ LV2_Atom_Forge forge;
+ lv2_atom_forge_init(&forge, &map);
+ lv2_atom_forge_set_buffer(&forge, buf2, BUF_SIZE);
+ assert(lv2_osc_forge_packet(&forge, &osc_urid, &map, buf0, size));
+
+ // check deforge
+ lv2_osc_writer_initialize(writer, buf1, BUF_SIZE);
+ assert(lv2_osc_writer_packet(writer, &osc_urid, &unmap, obj2->atom.size, &obj2->body));
+
+ // check deforged against raw bytes
+ assert(lv2_osc_writer_finalize(writer, &len) == buf1);
+ assert(len == size);
+ assert(memcmp(raw, buf1, size) == 0);
+}
+
+static void
+test_0_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ _test_a(writer, raw_0, sizeof(raw_0));
+}
+
+static void
+test_1_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "ifs",
+ 12, 3.4f, "world"));
+ _test_a(writer, raw_1, sizeof(raw_1));
+}
+
+static void
+test_2_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "hdS",
+ 12, 3.4, "http://example.com"));
+ _test_a(writer, raw_2, sizeof(raw_2));
+}
+
+static void
+test_3_a(LV2_OSC_Writer *writer)
+{
+ assert(lv2_osc_writer_message_vararg(writer, "/ping", "TFNI"));
+ _test_a(writer, raw_3, sizeof(raw_3));
+}
+
+static void
+test_4_a(LV2_OSC_Writer *writer)
+{
+ uint8_t m [] = {0x00, 0x90, 24, 0x7f};
+ assert(lv2_osc_writer_message_vararg(writer, "/midi", "m", 4, m));
+ _test_a(writer, raw_4, sizeof(raw_4));
+}
+
+static void
+test_5_a(LV2_OSC_Writer *writer)
+{
+ uint8_t b [] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6};
+ assert(lv2_osc_writer_message_vararg(writer, "/blob", "b", 6, b));
+ _test_a(writer, raw_5, sizeof(raw_5));
+}
+
+static void
+test_6_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl, frame_itm;
+
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl, LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl));
+
+ _test_a(writer, raw_6, sizeof(raw_6));
+}
+
+static void
+test_7_a(LV2_OSC_Writer *writer)
+{
+ LV2_OSC_Writer_Frame frame_bndl[2], frame_itm[2];
+
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl[0], LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(lv2_osc_writer_push_bundle(writer, &frame_bndl[1], LV2_OSC_IMMEDIATE));
+ {
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[1]));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[1]));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl[1]));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[0]));
+
+ assert(lv2_osc_writer_push_item(writer, &frame_itm[0]));
+ {
+ assert(lv2_osc_writer_message_vararg(writer, "/", ""));
+ }
+ assert(lv2_osc_writer_pop_item(writer, &frame_itm[0]));
+ }
+ assert(lv2_osc_writer_pop_bundle(writer, &frame_bndl[0]));
+
+ _test_a(writer, raw_7, sizeof(raw_7));
+}
+
+static test_t tests [] = {
+ test_0_a,
+ test_1_a,
+ test_2_a,
+ test_3_a,
+ test_4_a,
+ test_5_a,
+ test_6_a,
+ test_7_a,
+
+ NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ LV2_OSC_Writer writer;
+
+ for(test_t *test=tests; *test; test++)
+ {
+ test_t cb = *test;
+
+ memset(buf0, 0x0, BUF_SIZE);
+ memset(buf1, 0x0, BUF_SIZE);
+
+ lv2_osc_writer_initialize(&writer, buf0, BUF_SIZE);
+
+ cb(&writer);
+ }
+
+ return 0;
+}
diff --git a/osc_inspector.c b/osc_inspector.c
new file mode 100644
index 0000000..f3ebb58
--- /dev/null
+++ b/osc_inspector.c
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sherlock.h>
+
+#include <osc.lv2/util.h>
+
+typedef struct _handle_t handle_t;
+
+struct _handle_t {
+ LV2_URID_Map *map;
+ const LV2_Atom_Sequence *control_in;
+ LV2_Atom_Sequence *control_out;
+ LV2_Atom_Sequence *notify;
+ LV2_Atom_Forge forge;
+
+ LV2_URID time_position;
+ LV2_URID time_frame;
+
+ LV2_OSC_URID osc_urid;
+
+ int64_t frame;
+
+ PROPS_T(props, MAX_NPROPS);
+ state_t state;
+ state_t stash;
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ int i;
+ handle_t *handle = calloc(1, sizeof(handle_t));
+ if(!handle)
+ return NULL;
+
+ for(i=0; features[i]; i++)
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = (LV2_URID_Map *)features[i]->data;
+
+ if(!handle->map)
+ {
+ fprintf(stderr, "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->time_position = handle->map->map(handle->map->handle, LV2_TIME__Position);
+ handle->time_frame = handle->map->map(handle->map->handle, LV2_TIME__frame);
+
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+
+ if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ if( !props_register(&handle->props, &stat_overwrite, &handle->state.overwrite, &handle->stash.overwrite)
+ || !props_register(&handle->props, &stat_block, &handle->state.block, &handle->stash.block)
+ || !props_register(&handle->props, &stat_follow, &handle->state.follow, &handle->stash.follow) )
+ {
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->control_out = (LV2_Atom_Sequence *)data;
+ break;
+ case 2:
+ handle->notify = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ handle_t *handle = (handle_t *)instance;
+ uint32_t capacity;
+ LV2_Atom_Forge *forge = &handle->forge;
+ LV2_Atom_Forge_Frame frame [3];
+ LV2_Atom_Forge_Ref ref;
+
+ // size of input sequence
+ const size_t size = lv2_atom_total_size(&handle->control_in->atom);
+
+ // copy whole input sequence to through port
+ capacity = handle->control_out->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->control_out, capacity);
+ ref = lv2_atom_forge_sequence_head(forge, frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+
+ // copy all events to through port
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_raw(forge, &obj->atom, lv2_atom_total_size(&obj->atom));
+ if(ref)
+ lv2_atom_forge_pad(forge, obj->atom.size);
+
+ if( !props_advance(&handle->props, forge, frames, obj, &ref)
+ && lv2_atom_forge_is_object_type(forge, obj->atom.type)
+ && (obj->body.otype == handle->time_position) )
+ {
+ const LV2_Atom_Long *time_frame = NULL;
+ lv2_atom_object_get(obj, handle->time_frame, &time_frame, NULL);
+ if(time_frame)
+ handle->frame = time_frame->body - frames;
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, frame);
+ else
+ lv2_atom_sequence_clear(handle->control_out);
+
+ // forge whole sequence as single event
+ capacity = handle->notify->atom.size;
+ lv2_atom_forge_set_buffer(forge, (uint8_t *)handle->notify, capacity);
+
+ bool has_osc = false;
+
+ ref = lv2_atom_forge_sequence_head(forge, &frame[0], 0);
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, 0);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &frame[1]);
+ if(ref)
+ ref = lv2_atom_forge_long(forge, handle->frame);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, nsamples);
+ if(ref)
+ ref = lv2_atom_forge_sequence_head(forge, &frame[2], 0);
+
+ // only serialize OSC events to UI
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(lv2_osc_is_message_or_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ has_osc = true;
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, ev->time.frames);
+ if(ref)
+ ref = lv2_atom_forge_write(forge, &ev->body, sizeof(LV2_Atom) + ev->body.size);
+ }
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[2]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[1]);
+ if(ref)
+ lv2_atom_forge_pop(forge, &frame[0]);
+ else
+ lv2_atom_sequence_clear(handle->notify);
+
+ if(!has_osc)
+ lv2_atom_sequence_clear(handle->notify);
+
+ handle->frame += nsamples;
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ handle_t *handle = (handle_t *)instance;
+
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ handle_t *handle = instance;
+
+ return props_save(&handle->props, &handle->forge, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ handle_t *handle = instance;
+
+ return props_restore(&handle->props, &handle->forge, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+ return NULL;
+}
+
+const LV2_Descriptor osc_inspector = {
+ .URI = SHERLOCK_OSC_INSPECTOR_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
diff --git a/osc_inspector_nk.c b/osc_inspector_nk.c
new file mode 100644
index 0000000..d4f6004
--- /dev/null
+++ b/osc_inspector_nk.c
@@ -0,0 +1,397 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <time.h>
+#include <math.h>
+#include <inttypes.h>
+
+#include <sherlock.h>
+#include <sherlock_nk.h>
+
+#include <osc.lv2/util.h>
+
+typedef struct _mem_t mem_t;
+
+struct _mem_t {
+ size_t size;
+ char *buf;
+};
+
+static inline void
+_shadow(struct nk_context *ctx, bool *shadow)
+{
+ if(*shadow)
+ {
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 10;
+ b.w += 5*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28));
+ }
+
+ *shadow = !*shadow;
+}
+
+static inline void
+_mem_printf(mem_t *mem, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ char *src;
+ if(vasprintf(&src, fmt, args) != -1)
+ {
+ const size_t sz = strlen(src);
+
+ mem->buf = realloc(mem->buf, mem->size + sz);
+ if(mem->buf)
+ {
+ char *dst = mem->buf + mem->size - 1;
+
+ strncpy(dst, src, sz + 1);
+ mem->size += sz;
+ }
+ else
+ mem->size = 0;
+
+ free(src);
+ }
+
+ va_end(args);
+}
+
+static void
+_osc_timetag(mem_t *mem, LV2_OSC_Timetag *tt)
+{
+ if(tt->integral <= 1UL)
+ {
+ _mem_printf(mem, "t:%s", "immediate");
+ }
+ else
+ {
+ const uint32_t us = floor(tt->fraction * 0x1p-32 * 1e6);
+ const time_t ttime = tt->integral - 0x83aa7e80;
+ const struct tm *ltime = localtime(&ttime);
+
+ char tmp [32];
+ if(strftime(tmp, 32, "%d-%b-%Y %T", ltime))
+ _mem_printf(mem, "t:%s.%06"PRIu32, tmp, us);
+ }
+}
+
+static void
+_osc_message(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj, float offset)
+{
+ const LV2_Atom_String *path = NULL;
+ const LV2_Atom_Tuple *args = NULL;
+ lv2_osc_message_get(&handle->osc_urid, obj, &path, &args);
+
+ mem_t mem = {
+ .size = 1,
+ .buf = calloc(1, sizeof(char))
+ };
+
+ LV2_ATOM_TUPLE_FOREACH(args, arg)
+ {
+ const LV2_OSC_Type type = lv2_osc_argument_type(&handle->osc_urid, arg);
+
+ switch(type)
+ {
+ case LV2_OSC_INT32:
+ {
+ int32_t i;
+ lv2_osc_int32_get(&handle->osc_urid, arg, &i);
+ _mem_printf(&mem, "i:%"PRIi32, i);
+ } break;
+ case LV2_OSC_FLOAT:
+ {
+ float f;
+ lv2_osc_float_get(&handle->osc_urid, arg, &f);
+ _mem_printf(&mem, "f:%f", f);
+ } break;
+ case LV2_OSC_STRING:
+ {
+ const char *s;
+ lv2_osc_string_get(&handle->osc_urid, arg, &s);
+ _mem_printf(&mem, "s:%s", s);
+ } break;
+ case LV2_OSC_BLOB:
+ {
+ uint32_t sz;
+ const uint8_t *b;
+ lv2_osc_blob_get(&handle->osc_urid, arg, &sz, &b);
+ _mem_printf(&mem, "b(%"PRIu32"):", sz);
+ for(unsigned i=0; i<sz; i++)
+ _mem_printf(&mem, "%02"PRIx8, b[i]);
+ } break;
+
+ case LV2_OSC_TRUE:
+ {
+ _mem_printf(&mem, "T:rue");
+ } break;
+ case LV2_OSC_FALSE:
+ {
+ _mem_printf(&mem, "F:alse");
+ } break;
+ case LV2_OSC_NIL:
+ {
+ _mem_printf(&mem, "N:il");
+ } break;
+ case LV2_OSC_IMPULSE:
+ {
+ _mem_printf(&mem, "I:impulse");
+ } break;
+
+ case LV2_OSC_INT64:
+ {
+ int64_t h;
+ lv2_osc_int64_get(&handle->osc_urid, arg, &h);
+ _mem_printf(&mem, "h:%"PRIi64, h);
+ } break;
+ case LV2_OSC_DOUBLE:
+ {
+ double d;
+ lv2_osc_double_get(&handle->osc_urid, arg, &d);
+ _mem_printf(&mem, "d:%lf", d);
+ } break;
+ case LV2_OSC_TIMETAG:
+ {
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(&handle->osc_urid, arg, &tt);
+ _osc_timetag(&mem, &tt);
+ } break;
+
+ case LV2_OSC_SYMBOL:
+ {
+ const char *S;
+ lv2_osc_symbol_get(&handle->osc_urid, arg, &S);
+ _mem_printf(&mem, "S:%s", S);
+ } break;
+ case LV2_OSC_CHAR:
+ {
+ char c;
+ lv2_osc_char_get(&handle->osc_urid, arg, &c);
+ _mem_printf(&mem, "c:%c", c);
+ } break;
+ case LV2_OSC_RGBA:
+ {
+ uint8_t r [4];
+ lv2_osc_rgba_get(&handle->osc_urid, arg, r+0, r+1, r+2, r+3);
+ _mem_printf(&mem, "r:");
+ for(unsigned i=0; i<4; i++)
+ _mem_printf(&mem, "%02"PRIx8, r[i]);
+ } break;
+ case LV2_OSC_MIDI:
+ {
+ uint32_t sz;
+ const uint8_t *m;
+ lv2_osc_midi_get(&handle->osc_urid, arg, &sz, &m);
+ _mem_printf(&mem, "m(%"PRIu32"):", sz);
+ for(unsigned i=0; i<sz; i++)
+ _mem_printf(&mem, "%02"PRIx8, m[i]);
+ } break;
+ }
+
+ _mem_printf(&mem, " ");
+ }
+
+ nk_layout_row_push(ctx, 0.4 - offset);
+ nk_label_colored(ctx, LV2_ATOM_BODY_CONST(path), NK_TEXT_LEFT, magenta);
+
+ nk_layout_row_push(ctx, 0.5);
+ if(mem.buf)
+ {
+ nk_label_colored(ctx, mem.buf, NK_TEXT_LEFT, white);
+ free(mem.buf);
+ }
+ else
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, obj->atom.size);
+
+ nk_layout_row_end(ctx);
+}
+
+static void
+_osc_packet(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj, float offset);
+
+static void
+_osc_bundle(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj, float offset)
+{
+ const float widget_h = handle->dy;
+
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+ lv2_osc_bundle_get(&handle->osc_urid, obj, &timetag, &items);
+
+ // format bundle timestamp
+ LV2_OSC_Timetag tt;
+ lv2_osc_timetag_get(&handle->osc_urid, &timetag->atom, &tt);
+
+ mem_t mem = {
+ .size = 1,
+ .buf = calloc(1, sizeof(char))
+ };
+
+ nk_layout_row_push(ctx, 0.4 - offset);
+ nk_label_colored(ctx, "#bundle", NK_TEXT_LEFT, red);
+
+ nk_layout_row_push(ctx, 0.5);
+ _osc_timetag(&mem, &tt);
+ if(mem.buf)
+ {
+ nk_label_colored(ctx, mem.buf, NK_TEXT_LEFT, white);
+ free(mem.buf);
+ }
+ else
+ _empty(ctx);
+
+ nk_layout_row_push(ctx, 0.1);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, obj->atom.size);
+
+ nk_layout_row_end(ctx);
+
+ offset += 0.025;
+ LV2_ATOM_TUPLE_FOREACH(items, item)
+ {
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 4);
+
+ nk_layout_row_push(ctx, offset);
+ _shadow(ctx, &handle->shadow);
+ _empty(ctx);
+
+ _osc_packet(handle, ctx, (const LV2_Atom_Object *)item, offset);
+ }
+}
+
+static void
+_osc_packet(plughandle_t *handle, struct nk_context *ctx, const LV2_Atom_Object *obj, float offset)
+{
+ if(lv2_osc_is_message_type(&handle->osc_urid, obj->body.otype))
+ {
+ _osc_message(handle, ctx, obj, offset);
+ }
+ else if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ _osc_bundle(handle, ctx, obj, offset);
+ }
+}
+
+void
+_osc_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data)
+{
+ plughandle_t *handle = data;
+
+ const float widget_h = handle->dy;
+ struct nk_style *style = &ctx->style;
+ const struct nk_vec2 window_padding = style->window.padding;
+ const struct nk_vec2 group_padding = style->window.group_padding;
+
+ if(nk_begin(ctx, "Window", wbounds, NK_WINDOW_NO_SCROLLBAR))
+ {
+ nk_window_set_bounds(ctx, wbounds);
+ struct nk_panel *panel= nk_window_get_panel(ctx);
+ struct nk_command_buffer *canvas = nk_window_get_canvas(ctx);
+
+ const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h;
+ nk_layout_row_dynamic(ctx, body_h, 1);
+ nk_flags flags = NK_WINDOW_BORDER;
+ if(handle->state.follow)
+ flags |= NK_WINDOW_NO_SCROLLBAR;
+ struct nk_list_view lview;
+ if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES)))
+ {
+ if(handle->state.follow)
+ {
+ lview.end = NK_MAX(handle->n_item, 0);
+ lview.begin = NK_MAX(lview.end - lview.count, 0);
+ }
+ handle->shadow = lview.begin % 2 == 0;
+ for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++)
+ {
+ item_t *itm = handle->items[l];
+
+ switch(itm->type)
+ {
+ case ITEM_TYPE_NONE:
+ {
+ // skip, was bundle payload
+ } break;
+ case ITEM_TYPE_FRAME:
+ {
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ struct nk_rect b = nk_widget_bounds(ctx);
+ b.x -= group_padding.x;
+ b.w *= 3;
+ b.w += 4*group_padding.x;
+ nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18));
+ }
+
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset);
+ nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter);
+ nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples);
+ } break;
+
+ case ITEM_TYPE_EVENT:
+ {
+ LV2_Atom_Event *ev = &itm->event.ev;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ const int64_t frames = ev->time.frames;
+ const float off = 0.1;
+
+ nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 4);
+
+ nk_layout_row_push(ctx, off);
+ _shadow(ctx, &handle->shadow);
+ nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames);
+
+ _osc_packet(handle, ctx, obj, off);
+ } break;
+ }
+ }
+
+ nk_list_view_end(&lview);
+ }
+
+ nk_layout_row_dynamic(ctx, widget_h, 3);
+ {
+ if(nk_checkbox_label(ctx, "overwrite", &handle->state.overwrite))
+ _toggle(handle, handle->urid.overwrite, handle->state.overwrite, true);
+ if(nk_checkbox_label(ctx, "block", &handle->state.block))
+ _toggle(handle, handle->urid.block, handle->state.block, true);
+ if(nk_checkbox_label(ctx, "follow", &handle->state.follow))
+ _toggle(handle, handle->urid.follow, handle->state.follow, true);
+ }
+
+ const bool max_reached = handle->n_item >= MAX_LINES;
+ nk_layout_row_dynamic(ctx, widget_h, 2);
+ if(nk_button_symbol_label(ctx,
+ max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE,
+ "clear", NK_TEXT_LEFT))
+ {
+ _clear(handle);
+ }
+ nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT);
+ }
+ nk_end(ctx);
+}
diff --git a/props.lv2/CMakeLists.txt b/props.lv2/CMakeLists.txt
new file mode 100644
index 0000000..2fff6f0
--- /dev/null
+++ b/props.lv2/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(props.lv2)
+
+include_directories(${PROJECT_SOURCE_DIR})
+include_directories(${PROJECT_BINARY_DIR})
+
+set(CMAKE_C_FLAGS "-std=gnu99 -Wextra -Wno-unused-parameter -ffast-math -fvisibility=hidden ${CMAKE_C_FLAGS}")
+set(CMAKE_C_FLAGS "-Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes ${CMAKE_C_FLAGS}")
+set(CMAKE_MODULE_LINKER_FLAGS "-Wl,-z,nodelete ${CMAKE_MODULE_LINKER_FLAGS}")
+add_definitions("-D_GNU_SOURCE=1") # asprintf
+
+set(PROPS_MAJOR_VERSION 0)
+set(PROPS_MINOR_VERSION 1)
+set(PROPS_MICRO_VERSION 1)
+set(PROPS_VERSION "${PROPS_MAJOR_VERSION}.${PROPS_MINOR_VERSION}.${PROPS_MICRO_VERSION}")
+add_definitions("-DPROPS_VERSION=\"${PROPS_VERSION}\"")
+
+set(DEST lib/lv2/props.lv2)
+if(WIN32)
+ set(LIB_EXT ".dll")
+elseif(APPLE)
+ set(LIB_EXT ".so")
+else()
+ set(LIB_EXT ".so")
+endif()
+
+find_package(PkgConfig) # ${PKG_CONFIG_FOUND}
+
+# props
+add_library(props MODULE
+ test/props.c)
+target_link_libraries(props ${LIBS})
+set_target_properties(props PROPERTIES PREFIX "")
+install(TARGETS props DESTINATION ${DEST})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/props.ttl DESTINATION ${DEST})
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/test/chunk.bin DESTINATION ${DEST})
+
+# manifest
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/test/manifest.ttl.in ${PROJECT_BINARY_DIR}/manifest.ttl)
+install(FILES ${PROJECT_BINARY_DIR}/manifest.ttl DESTINATION ${DEST})
diff --git a/props.lv2/COPYING b/props.lv2/COPYING
new file mode 100644
index 0000000..ddb9a46
--- /dev/null
+++ b/props.lv2/COPYING
@@ -0,0 +1,201 @@
+ The Artistic License 2.0
+
+ Copyright (c) 2000-2006, The Perl Foundation.
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+Preamble
+
+This license establishes the terms under which a given free software
+Package may be copied, modified, distributed, and/or redistributed.
+The intent is that the Copyright Holder maintains some artistic
+control over the development of that Package while still keeping the
+Package available as open source and free software.
+
+You are always permitted to make arrangements wholly outside of this
+license directly with the Copyright Holder of a given Package. If the
+terms of this license do not permit the full use that you propose to
+make of the Package, you should contact the Copyright Holder and seek
+a different licensing arrangement.
+
+Definitions
+
+ "Copyright Holder" means the individual(s) or organization(s)
+ named in the copyright notice for the entire Package.
+
+ "Contributor" means any party that has contributed code or other
+ material to the Package, in accordance with the Copyright Holder's
+ procedures.
+
+ "You" and "your" means any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Package" means the collection of files distributed by the
+ Copyright Holder, and derivatives of that collection and/or of
+ those files. A given Package may consist of either the Standard
+ Version, or a Modified Version.
+
+ "Distribute" means providing a copy of the Package or making it
+ accessible to anyone else, or in the case of a company or
+ organization, to others outside of your company or organization.
+
+ "Distributor Fee" means any fee that you charge for Distributing
+ this Package or providing support for this Package to another
+ party. It does not mean licensing fees.
+
+ "Standard Version" refers to the Package if it has not been
+ modified, or has been modified only in ways explicitly requested
+ by the Copyright Holder.
+
+ "Modified Version" means the Package, if it has been changed, and
+ such changes were not explicitly requested by the Copyright
+ Holder.
+
+ "Original License" means this Artistic License as Distributed with
+ the Standard Version of the Package, in its current version or as
+ it may be modified by The Perl Foundation in the future.
+
+ "Source" form means the source code, documentation source, and
+ configuration files for the Package.
+
+ "Compiled" form means the compiled bytecode, object code, binary,
+ or any other form resulting from mechanical transformation or
+ translation of the Source form.
+
+
+Permission for Use and Modification Without Distribution
+
+(1) You are permitted to use the Standard Version and create and use
+Modified Versions for any purpose without restriction, provided that
+you do not Distribute the Modified Version.
+
+
+Permissions for Redistribution of the Standard Version
+
+(2) You may Distribute verbatim copies of the Source form of the
+Standard Version of this Package in any medium without restriction,
+either gratis or for a Distributor Fee, provided that you duplicate
+all of the original copyright notices and associated disclaimers. At
+your discretion, such verbatim copies may or may not include a
+Compiled form of the Package.
+
+(3) You may apply any bug fixes, portability changes, and other
+modifications made available from the Copyright Holder. The resulting
+Package will still be considered the Standard Version, and as such
+will be subject to the Original License.
+
+
+Distribution of Modified Versions of the Package as Source
+
+(4) You may Distribute your Modified Version as Source (either gratis
+or for a Distributor Fee, and with or without a Compiled form of the
+Modified Version) provided that you clearly document how it differs
+from the Standard Version, including, but not limited to, documenting
+any non-standard features, executables, or modules, and provided that
+you do at least ONE of the following:
+
+ (a) make the Modified Version available to the Copyright Holder
+ of the Standard Version, under the Original License, so that the
+ Copyright Holder may include your modifications in the Standard
+ Version.
+
+ (b) ensure that installation of your Modified Version does not
+ prevent the user installing or running the Standard Version. In
+ addition, the Modified Version must bear a name that is different
+ from the name of the Standard Version.
+
+ (c) allow anyone who receives a copy of the Modified Version to
+ make the Source form of the Modified Version available to others
+ under
+
+ (i) the Original License or
+
+ (ii) a license that permits the licensee to freely copy,
+ modify and redistribute the Modified Version using the same
+ licensing terms that apply to the copy that the licensee
+ received, and requires that the Source form of the Modified
+ Version, and of any works derived from it, be made freely
+ available in that license fees are prohibited but Distributor
+ Fees are allowed.
+
+
+Distribution of Compiled Forms of the Standard Version
+or Modified Versions without the Source
+
+(5) You may Distribute Compiled forms of the Standard Version without
+the Source, provided that you include complete instructions on how to
+get the Source of the Standard Version. Such instructions must be
+valid at the time of your distribution. If these instructions, at any
+time while you are carrying out such distribution, become invalid, you
+must provide new instructions on demand or cease further distribution.
+If you provide valid instructions or cease distribution within thirty
+days after you become aware that the instructions are invalid, then
+you do not forfeit any of your rights under this license.
+
+(6) You may Distribute a Modified Version in Compiled form without
+the Source, provided that you comply with Section 4 with respect to
+the Source of the Modified Version.
+
+
+Aggregating or Linking the Package
+
+(7) You may aggregate the Package (either the Standard Version or
+Modified Version) with other packages and Distribute the resulting
+aggregation provided that you do not charge a licensing fee for the
+Package. Distributor Fees are permitted, and licensing fees for other
+components in the aggregation are permitted. The terms of this license
+apply to the use and Distribution of the Standard or Modified Versions
+as included in the aggregation.
+
+(8) You are permitted to link Modified and Standard Versions with
+other works, to embed the Package in a larger work of your own, or to
+build stand-alone binary or bytecode versions of applications that
+include the Package, and Distribute the result without restriction,
+provided the result does not expose a direct interface to the Package.
+
+
+Items That are Not Considered Part of a Modified Version
+
+(9) Works (including, but not limited to, modules and scripts) that
+merely extend or make use of the Package, do not, by themselves, cause
+the Package to be a Modified Version. In addition, such works are not
+considered parts of the Package itself, and are not subject to the
+terms of this license.
+
+
+General Provisions
+
+(10) Any use, modification, and distribution of the Standard or
+Modified Versions is governed by this Artistic License. By using,
+modifying or distributing the Package, you accept this license. Do not
+use, modify, or distribute the Package, if you do not accept this
+license.
+
+(11) If your Modified Version has been derived from a Modified
+Version made by someone other than you, you are nevertheless required
+to ensure that your Modified Version complies with the requirements of
+this license.
+
+(12) This license does not grant you the right to use any trademark,
+service mark, tradename, or logo of the Copyright Holder.
+
+(13) This license includes the non-exclusive, worldwide,
+free-of-charge patent license to make, have made, use, offer to sell,
+sell, import and otherwise transfer the Package with respect to any
+patent claims licensable by the Copyright Holder that are necessarily
+infringed by the Package. If you institute patent litigation
+(including a cross-claim or counterclaim) against any party alleging
+that the Package constitutes direct or contributory patent
+infringement, then this Artistic License to you shall terminate on the
+date that such litigation is filed.
+
+(14) Disclaimer of Warranty:
+THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/props.lv2/README.md b/props.lv2/README.md
new file mode 100644
index 0000000..08466d2
--- /dev/null
+++ b/props.lv2/README.md
@@ -0,0 +1,20 @@
+# Props.lv2
+
+## Utility header for property based LV2 plugins
+
+### License
+
+Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+
+This is free software: you can redistribute it and/or modify
+it under the terms of the Artistic License 2.0 as published by
+The Perl Foundation.
+
+This source is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+Artistic License 2.0 for more details.
+
+You should have received a copy of the Artistic License 2.0
+along the source as a COPYING file. If not, obtain it from
+<http://www.perlfoundation.org/artistic_license_2_0>.
diff --git a/props.lv2/props.h b/props.lv2/props.h
new file mode 100644
index 0000000..8f46d46
--- /dev/null
+++ b/props.lv2/props.h
@@ -0,0 +1,1453 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _LV2_PROPS_H_
+#define _LV2_PROPS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stdatomic.h>
+#include <stdio.h>
+
+#include <lv2/lv2plug.in/ns/lv2core/lv2.h>
+#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
+#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
+#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
+#include <lv2/lv2plug.in/ns/ext/state/state.h>
+#include <lv2/lv2plug.in/ns/extensions/units/units.h>
+
+/*****************************************************************************
+ * API START
+ *****************************************************************************/
+
+// definitions
+#define PROPS_TYPE_N 10
+
+// unions
+typedef union _props_raw_t props_raw_t;
+
+// enumerations
+typedef enum _props_mode_t props_mode_t;
+typedef enum _props_event_t props_event_t;
+
+// structures
+typedef struct _props_scale_point_t props_scale_point_t;
+typedef struct _props_def_t props_def_t;
+typedef struct _props_type_t props_type_t;
+typedef struct _props_impl_t props_impl_t;
+typedef struct _props_t props_t;
+
+// function callbacks
+typedef void (*props_event_cb_t)(
+ void *data,
+ LV2_Atom_Forge *forge,
+ int64_t frames,
+ props_event_t event,
+ props_impl_t *impl);
+
+typedef uint32_t (*props_type_size_cb_t)(
+ const void *value);
+
+typedef LV2_Atom_Forge_Ref (*props_type_get_cb_t)(
+ LV2_Atom_Forge *forge,
+ const void *value);
+
+typedef void (*props_type_set_cb_t)(
+ props_impl_t *impl,
+ void *value,
+ LV2_URID new_type,
+ uint32_t sz,
+ const void *new_value);
+
+union _props_raw_t {
+ const int32_t i; // Int
+ const int64_t h; // Long
+ const float f; // Float
+ const double d; // Double
+ const int32_t b; // Bool
+ const uint32_t u; // URID
+ //TODO more types
+};
+
+enum _props_mode_t {
+ PROP_MODE_STATIC = 0,
+ PROP_MODE_DYNAMIC = 1
+};
+
+enum _props_event_t {
+ PROP_EVENT_GET = (1 << 0),
+ PROP_EVENT_SET = (1 << 1),
+ PROP_EVENT_SAVE = (1 << 2),
+ PROP_EVENT_RESTORE = (1 << 3),
+ PROP_EVENT_REGISTER = (1 << 4)
+};
+
+#define PROP_EVENT_NONE (0)
+#define PROP_EVENT_READ (PROP_EVENT_GET | PROP_EVENT_SAVE)
+#define PROP_EVENT_WRITE (PROP_EVENT_SET | PROP_EVENT_RESTORE)
+#define PROP_EVENT_RW (PROP_EVENT_READ | PROP_EVENT_WRITE)
+#define PROP_EVENT_ALL (PROP_EVENT_RW | PROP_EVENT_REGISTER)
+
+struct _props_scale_point_t {
+ const char *label;
+ props_raw_t value;
+};
+
+struct _props_def_t {
+ const char *property;
+ const char *type;
+ const char *access;
+ const char *unit;
+ props_mode_t mode;
+ props_event_t event_mask;
+ props_event_cb_t event_cb;
+ uint32_t max_size;
+
+ const char *label;
+ const char *comment;
+ props_raw_t minimum;
+ props_raw_t maximum;
+ const props_scale_point_t *scale_points;
+};
+
+struct _props_type_t {
+ LV2_URID urid;
+ uint32_t size;
+ props_type_size_cb_t size_cb;
+ props_type_get_cb_t get_cb;
+ props_type_set_cb_t set_cb;
+};
+
+struct _props_impl_t {
+ LV2_URID property;
+ LV2_URID access;
+ LV2_URID unit;
+ const props_t *props;
+ const props_type_t *type;
+ const props_def_t *def;
+ void *value;
+ void *stash;
+ atomic_flag lock;
+ bool stashing;
+};
+
+struct _props_t {
+ struct {
+ LV2_URID subject;
+
+ LV2_URID patch_get;
+ LV2_URID patch_set;
+ LV2_URID patch_put;
+ LV2_URID patch_patch;
+ LV2_URID patch_wildcard;
+ LV2_URID patch_add;
+ LV2_URID patch_remove;
+ LV2_URID patch_subject;
+ LV2_URID patch_body;
+ LV2_URID patch_property;
+ LV2_URID patch_value;
+ LV2_URID patch_writable;
+ LV2_URID patch_readable;
+ LV2_URID patch_sequence;
+ LV2_URID patch_error;
+ LV2_URID patch_ack;
+
+ LV2_URID rdf_value;
+
+ LV2_URID rdfs_label;
+ LV2_URID rdfs_range;
+ LV2_URID rdfs_comment;
+
+ LV2_URID lv2_minimum;
+ LV2_URID lv2_maximum;
+ LV2_URID lv2_scale_point;
+
+ LV2_URID atom_int;
+ LV2_URID atom_long;
+ LV2_URID atom_float;
+ LV2_URID atom_double;
+ LV2_URID atom_bool;
+ LV2_URID atom_urid;
+ LV2_URID atom_string;
+ LV2_URID atom_path;
+ LV2_URID atom_uri;
+ LV2_URID atom_chunk;
+
+ LV2_URID units_unit;
+ } urid;
+
+ LV2_URID_Map *map;
+ void *data;
+
+ props_type_t types [PROPS_TYPE_N];
+
+ bool stashing;
+
+ unsigned max_size;
+ unsigned max_nimpls;
+ unsigned nimpls;
+ props_impl_t impls [0];
+};
+
+#define PROPS_T(PROPS, MAX_NIMPLS) \
+ props_t (PROPS); \
+ props_impl_t _impls [(MAX_NIMPLS)];
+
+// rt-safe
+static inline int
+props_init(props_t *props, const size_t max_nimpls, const char *subject,
+ LV2_URID_Map *map, void *data);
+
+// rt-safe
+static inline LV2_URID
+props_register(props_t *props, const props_def_t *def, void *value, void *stash);
+
+// rt-safe
+static inline int
+props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, LV2_URID property,
+ LV2_Atom_Forge_Ref *ref);
+
+// rt-safe
+static inline void
+props_stash(props_t *props, LV2_URID property);
+
+// non-rt
+static inline LV2_State_Status
+props_save(props_t *props, LV2_Atom_Forge *forge, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features);
+
+// non-rt
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_Atom_Forge *forge, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features);
+
+/*****************************************************************************
+ * API END
+ *****************************************************************************/
+
+static inline void
+_impl_spin_lock(props_impl_t *impl)
+{
+ while(atomic_flag_test_and_set_explicit(&impl->lock, memory_order_acquire))
+ {
+ // spin
+ }
+}
+
+static inline bool
+_impl_try_lock(props_impl_t *impl)
+{
+ return atomic_flag_test_and_set_explicit(&impl->lock, memory_order_acquire) == false;
+}
+
+static inline void
+_impl_unlock(props_impl_t *impl)
+{
+ atomic_flag_clear_explicit(&impl->lock, memory_order_release);
+}
+
+static inline uint32_t
+_impl_size_get(props_impl_t *impl)
+{
+ return impl->type->size_cb
+ ? impl->type->size_cb(impl->value)
+ : impl->type->size;
+}
+
+static LV2_Atom_Forge_Ref
+_props_int_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_int(forge, *(const int32_t *)value);
+}
+static LV2_Atom_Forge_Ref
+_props_bool_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_bool(forge, *(const int32_t *)value);
+}
+static void
+_props_int_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ int32_t *ref = value;
+
+ if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_long_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_long(forge, *(const int64_t *)value);
+}
+static void
+_props_long_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ int64_t *ref = value;
+
+ if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_float_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_float(forge, *(const float *)value);
+}
+static void
+_props_float_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ float *ref = value;
+
+ if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_double_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_double(forge, *(const double *)value);
+}
+static void
+_props_double_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ double *ref = value;
+
+ if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+}
+
+static LV2_Atom_Forge_Ref
+_props_urid_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_urid(forge, *(const uint32_t *)value);
+}
+static void
+_props_urid_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+ uint32_t *ref = value;
+
+ if(new_type == props->urid.atom_urid)
+ *ref = *(const uint32_t *)new_value;
+
+ else if(new_type == props->urid.atom_int)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_long)
+ *ref = *(const int64_t *)new_value;
+ else if(new_type == props->urid.atom_bool)
+ *ref = *(const int32_t *)new_value;
+ else if(new_type == props->urid.atom_float)
+ *ref = *(const float *)new_value;
+ else if(new_type == props->urid.atom_double)
+ *ref = *(const double *)new_value;
+}
+
+static uint32_t
+_props_string_size_cb(const void *value)
+{
+ return strlen((const char *)value) + 1;
+}
+static LV2_Atom_Forge_Ref
+_props_string_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_string(forge, (const char *)value, strlen((const char *)value));
+}
+static LV2_Atom_Forge_Ref
+_props_path_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_path(forge, (const char *)value, strlen((const char *)value));
+}
+static LV2_Atom_Forge_Ref
+_props_uri_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ return lv2_atom_forge_uri(forge, (const char *)value, strlen((const char *)value));
+}
+static void
+_props_string_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+
+ if( (new_type == props->urid.atom_string)
+ || (new_type == props->urid.atom_path)
+ || (new_type == props->urid.atom_uri) )
+ strncpy((char *)value, (const char *)new_value, impl->def->max_size);
+}
+
+static uint32_t
+_props_chunk_size_cb(const void *value)
+{
+ const uint32_t sz = *(uint32_t *)value;
+ return sz;
+}
+static LV2_Atom_Forge_Ref
+_props_chunk_get_cb(LV2_Atom_Forge *forge, const void *value)
+{
+ const uint32_t sz = *(uint32_t *)value;
+ const uint8_t *src = value + sizeof(uint32_t);
+ LV2_Atom_Forge_Ref ref;
+
+ return (ref = lv2_atom_forge_atom(forge, sz, forge->Chunk))
+ && (ref = lv2_atom_forge_write(forge, src, sz));
+}
+static void
+_props_chunk_set_cb(props_impl_t *impl, void *value,
+ LV2_URID new_type, uint32_t sz, const void *new_value)
+{
+ const props_t *props = impl->props;
+
+ if(new_type == props->urid.atom_chunk)
+ {
+ *(uint32_t *)value = sz; // set chunk size
+ uint8_t *dst = value + sizeof(uint32_t);
+ const uint32_t msz = sz < impl->def->max_size - sizeof(uint32_t)
+ ? sz
+ : impl->def->max_size - sizeof(uint32_t);
+ memcpy(dst, new_value, msz);
+ }
+}
+
+static inline void
+_type_qsort(props_type_t *a, unsigned n)
+{
+ if(n < 2)
+ return;
+
+ const props_type_t *p = &a[n/2];
+
+ unsigned i, j;
+ for(i=0, j=n-1; ; i++, j--)
+ {
+ while(a[i].urid < p->urid)
+ i++;
+
+ while(p->urid < a[j].urid)
+ j--;
+
+ if(i >= j)
+ break;
+
+ const props_type_t t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+
+ _type_qsort(a, i);
+ _type_qsort(&a[i], n - i);
+}
+
+static inline props_type_t *
+_type_bsearch(LV2_URID p, props_type_t *a, unsigned n)
+{
+ props_type_t *base = a;
+
+ for(unsigned N = n, half; N > 1; N -= half)
+ {
+ half = N/2;
+ props_type_t *dst = &base[half];
+ base = (dst->urid > p) ? base : dst;
+ }
+
+ return (base->urid == p) ? base : NULL;
+}
+
+static inline void
+_impl_qsort(props_impl_t *a, unsigned n)
+{
+ if(n < 2)
+ return;
+
+ const props_impl_t *p = &a[n/2];
+
+ unsigned i, j;
+ for(i=0, j=n-1; ; i++, j--)
+ {
+ while(a[i].property < p->property)
+ i++;
+
+ while(p->property < a[j].property)
+ j--;
+
+ if(i >= j)
+ break;
+
+ const props_impl_t t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+ }
+
+ _impl_qsort(a, i);
+ _impl_qsort(&a[i], n - i);
+}
+
+static inline props_impl_t *
+_impl_bsearch(LV2_URID p, props_impl_t *a, unsigned n)
+{
+ props_impl_t *base = a;
+
+ for(unsigned N = n, half; N > 1; N -= half)
+ {
+ half = N/2;
+ props_impl_t *dst = &base[half];
+ base = (dst->property > p) ? base : dst;
+ }
+
+ return (base->property == p) ? base : NULL;
+}
+
+static inline props_impl_t *
+_props_impl_search(props_t *props, LV2_URID property)
+{
+ return _impl_bsearch(property, props->impls, props->nimpls);
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_get(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ props_impl_t *impl, int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_set);
+ {
+ if(props->urid.subject) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.subject);
+ }
+
+ if(sequence_num) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_property);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+
+ if(ref)
+ lv2_atom_forge_key(forge, props->urid.patch_value);
+ if(ref)
+ ref = impl->type->get_cb(forge, impl->value);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_error(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_error);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_ack(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, int32_t sequence_num)
+{
+ LV2_Atom_Forge_Frame obj_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_ack);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline void
+_props_stash(props_t *props, props_impl_t *impl)
+{
+ if(_impl_try_lock(impl))
+ {
+ const uint32_t size = _impl_size_get(impl);
+ memcpy(impl->stash, impl->value, size);
+
+ _impl_unlock(impl);
+ }
+ else
+ {
+ impl->stashing = true;
+ props->stashing= true;
+ }
+}
+
+static inline void
+_props_set(props_t *props, props_impl_t *impl, LV2_URID type, uint32_t sz, const void *value)
+{
+ impl->type->set_cb(impl, impl->value, type, sz, value);
+ _props_stash(props, impl);
+}
+
+static inline LV2_Atom_Forge_Ref
+_props_reg(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ props_impl_t *impl, int32_t sequence_num)
+{
+ const props_def_t *def = impl->def;
+ LV2_Atom_Forge_Frame obj_frame;
+ LV2_Atom_Forge_Frame add_frame;
+ LV2_Atom_Forge_Frame remove_frame;
+
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_patch);
+ {
+ if(props->urid.subject) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.subject);
+ }
+
+ if(sequence_num) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_remove);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->access);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_add);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, impl->access);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_frame_time(forge, frames);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &obj_frame, 0, props->urid.patch_patch);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_subject);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->property);
+
+ if(sequence_num) // is optional
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_sequence);
+ if(ref)
+ ref = lv2_atom_forge_int(forge, sequence_num);
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_remove);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &remove_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_range);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_label);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_comment);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_minimum);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_maximum);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.units_unit);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_scale_point);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, props->urid.patch_wildcard);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &remove_frame);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.patch_add);
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &add_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_range);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->type->urid);
+
+ if(def->label)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_label);
+ if(ref)
+ ref = lv2_atom_forge_string(forge, def->label, strlen(def->label));
+ }
+
+ if(def->comment)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_comment);
+ if(ref)
+ ref = lv2_atom_forge_string(forge, def->comment, strlen(def->comment));
+ }
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_minimum);
+ if(ref)
+ ref = impl->type->get_cb(forge, &def->minimum);
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_maximum);
+ if(ref)
+ ref = impl->type->get_cb(forge, &def->maximum);
+
+ if(props->urid.units_unit)
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.units_unit);
+ if(ref)
+ ref = lv2_atom_forge_urid(forge, impl->unit);
+ }
+
+ if(def->scale_points)
+ {
+ LV2_Atom_Forge_Frame tuple_frame;
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.lv2_scale_point);
+ if(ref)
+ ref = lv2_atom_forge_tuple(forge, &tuple_frame);
+
+ for(const props_scale_point_t *sp = def->scale_points; sp->label; sp++)
+ {
+ LV2_Atom_Forge_Frame scale_point_frame;
+
+ if(ref)
+ ref = lv2_atom_forge_object(forge, &scale_point_frame, 0, 0);
+ {
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdfs_label);
+ if(ref)
+ ref = lv2_atom_forge_string(forge, sp->label, strlen(sp->label));
+
+ if(ref)
+ ref = lv2_atom_forge_key(forge, props->urid.rdf_value);
+ if(ref)
+ ref = impl->type->get_cb(forge, &sp->value);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &scale_point_frame);
+ }
+
+ if(ref)
+ lv2_atom_forge_pop(forge, &tuple_frame);
+ }
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &add_frame);
+ }
+ if(ref)
+ lv2_atom_forge_pop(forge, &obj_frame);
+
+ return ref;
+}
+
+static inline int
+props_init(props_t *props, const size_t max_nimpls, const char *subject,
+ LV2_URID_Map *map, void *data)
+{
+ if(!map)
+ return 0;
+
+ props->nimpls = 0;
+ props->max_nimpls = max_nimpls;
+ props->map = map;
+ props->data = data;
+
+ props->urid.subject = subject ? map->map(map->handle, subject) : 0;
+
+ props->urid.patch_get = map->map(map->handle, LV2_PATCH__Get);
+ props->urid.patch_set = map->map(map->handle, LV2_PATCH__Set);
+ props->urid.patch_put = map->map(map->handle, LV2_PATCH__Put);
+ props->urid.patch_patch = map->map(map->handle, LV2_PATCH__Patch);
+ props->urid.patch_wildcard = map->map(map->handle, LV2_PATCH__wildcard);
+ props->urid.patch_add = map->map(map->handle, LV2_PATCH__add);
+ props->urid.patch_remove = map->map(map->handle, LV2_PATCH__remove);
+ props->urid.patch_subject = map->map(map->handle, LV2_PATCH__subject);
+ props->urid.patch_body = map->map(map->handle, LV2_PATCH__body);
+ props->urid.patch_property = map->map(map->handle, LV2_PATCH__property);
+ props->urid.patch_value = map->map(map->handle, LV2_PATCH__value);
+ props->urid.patch_writable = map->map(map->handle, LV2_PATCH__writable);
+ props->urid.patch_readable = map->map(map->handle, LV2_PATCH__readable);
+ props->urid.patch_sequence = map->map(map->handle, LV2_PATCH__sequenceNumber);
+ props->urid.patch_ack = map->map(map->handle, LV2_PATCH__Ack);
+ props->urid.patch_error = map->map(map->handle, LV2_PATCH__Error);
+
+ props->urid.rdf_value = map->map(map->handle,
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#value");
+
+ props->urid.rdfs_label = map->map(map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#label");
+ props->urid.rdfs_range = map->map(map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#range");
+ props->urid.rdfs_comment = map->map(map->handle,
+ "http://www.w3.org/2000/01/rdf-schema#comment");
+
+ props->urid.lv2_minimum = map->map(map->handle, LV2_CORE__minimum);
+ props->urid.lv2_maximum = map->map(map->handle, LV2_CORE__maximum);
+ props->urid.lv2_scale_point = map->map(map->handle, LV2_CORE__scalePoint);
+
+ props->urid.atom_int = map->map(map->handle, LV2_ATOM__Int);
+ props->urid.atom_long = map->map(map->handle, LV2_ATOM__Long);
+ props->urid.atom_float = map->map(map->handle, LV2_ATOM__Float);
+ props->urid.atom_double = map->map(map->handle, LV2_ATOM__Double);
+ props->urid.atom_bool = map->map(map->handle, LV2_ATOM__Bool);
+ props->urid.atom_urid = map->map(map->handle, LV2_ATOM__URID);
+ props->urid.atom_string = map->map(map->handle, LV2_ATOM__String);
+ props->urid.atom_path = map->map(map->handle, LV2_ATOM__Path);
+ props->urid.atom_uri = map->map(map->handle, LV2_ATOM__URI);
+ props->urid.atom_chunk = map->map(map->handle, LV2_ATOM__Chunk);
+
+ props->urid.units_unit = map->map(map->handle, LV2_UNITS__unit);
+
+ // Int
+ unsigned ptr = 0;
+ props->types[ptr].urid = props->urid.atom_int;
+ props->types[ptr].size = sizeof(int32_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_int_get_cb;
+ props->types[ptr].set_cb = _props_int_set_cb;
+ ptr++;
+
+ // Long
+ props->types[ptr].urid = props->urid.atom_long;
+ props->types[ptr].size = sizeof(int64_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_long_get_cb;
+ props->types[ptr].set_cb = _props_long_set_cb;
+ ptr++;
+
+ // Float
+ props->types[ptr].urid = props->urid.atom_float;
+ props->types[ptr].size = sizeof(float);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_float_get_cb;
+ props->types[ptr].set_cb = _props_float_set_cb;
+ ptr++;
+
+ // double
+ props->types[ptr].urid = props->urid.atom_double;
+ props->types[ptr].size = sizeof(double);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_double_get_cb;
+ props->types[ptr].set_cb = _props_double_set_cb;
+ ptr++;
+
+ // Bool
+ props->types[ptr].urid = props->urid.atom_bool;
+ props->types[ptr].size = sizeof(int32_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_bool_get_cb;
+ props->types[ptr].set_cb = _props_int_set_cb;
+ ptr++;
+
+ // URID
+ props->types[ptr].urid = props->urid.atom_urid;
+ props->types[ptr].size = sizeof(uint32_t);
+ props->types[ptr].size_cb = NULL;
+ props->types[ptr].get_cb = _props_urid_get_cb;
+ props->types[ptr].set_cb = _props_urid_set_cb;
+ ptr++;
+
+ // String
+ props->types[ptr].urid = props->urid.atom_string;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_string_size_cb;
+ props->types[ptr].get_cb = _props_string_get_cb;
+ props->types[ptr].set_cb = _props_string_set_cb;
+ ptr++;
+
+ // Path
+ props->types[ptr].urid = props->urid.atom_path;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_string_size_cb;
+ props->types[ptr].get_cb = _props_path_get_cb;
+ props->types[ptr].set_cb = _props_string_set_cb;
+ ptr++;
+
+ // URI
+ props->types[ptr].urid = props->urid.atom_uri;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_string_size_cb;
+ props->types[ptr].get_cb = _props_uri_get_cb;
+ props->types[ptr].set_cb = _props_string_set_cb;
+ ptr++;
+
+ // URI
+ props->types[ptr].urid = props->urid.atom_chunk;
+ props->types[ptr].size = 0;
+ props->types[ptr].size_cb = _props_chunk_size_cb;
+ props->types[ptr].get_cb = _props_chunk_get_cb;
+ props->types[ptr].set_cb = _props_chunk_set_cb;
+ ptr++;
+
+ assert(ptr == PROPS_TYPE_N);
+ _type_qsort(props->types, PROPS_TYPE_N);
+
+ return 1;
+}
+
+static inline LV2_URID
+props_register(props_t *props, const props_def_t *def, void *value, void *stash)
+{
+ if(props->nimpls >= props->max_nimpls)
+ return 0;
+
+ if(!def || !def->property || !def->access || !def->type || !value || !stash)
+ return 0;
+
+ const LV2_URID type = props->map->map(props->map->handle, def->type);
+ const props_type_t *props_type = _type_bsearch(type, props->types, PROPS_TYPE_N);
+ const LV2_URID property = props->map->map(props->map->handle, def->property);
+ const LV2_URID access = props->map->map(props->map->handle, def->access);
+
+ if(!props_type || !property || !access)
+ return 0;
+
+ props_impl_t *impl = &props->impls[props->nimpls++];
+
+ impl->props = props;
+ impl->property = property;
+ impl->access = access;
+ impl->unit = def->unit ? props->map->map(props->map->handle, def->unit) : 0;
+ impl->type = props_type;
+ impl->def = def;
+ impl->value = value;
+ impl->stash = stash;
+ atomic_flag_clear_explicit(&impl->lock, memory_order_relaxed);
+
+ // update maximal value size
+ if(props_type->size && (props_type->size > props->max_size) )
+ props->max_size = props_type->size;
+ else if(def->max_size && (def->max_size > props->max_size) )
+ props->max_size = def->max_size;
+
+ _impl_qsort(props->impls, props->nimpls);
+
+ //TODO register?
+
+ return property;
+}
+
+static inline int
+props_advance(props_t *props, LV2_Atom_Forge *forge, uint32_t frames,
+ const LV2_Atom_Object *obj, LV2_Atom_Forge_Ref *ref)
+{
+ if(props->stashing)
+ {
+ props->stashing = false;
+
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->stashing)
+ {
+ impl->stashing= false;
+ _props_stash(props, impl);
+ }
+ }
+ }
+
+ if(!lv2_atom_forge_is_object_type(forge, obj->atom.type))
+ {
+ return 0;
+ }
+
+ if(obj->body.otype == props->urid.patch_get)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { props->urid.patch_subject, (const LV2_Atom **)&subject },
+ { props->urid.patch_property, (const LV2_Atom **)&property },
+ { props->urid.patch_sequence, (const LV2_Atom **)&sequence },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+ lv2_atom_object_query(obj, q);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(!property)
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+ const props_def_t *def = impl->def;
+
+ if(impl->def->mode == PROP_MODE_DYNAMIC)
+ {
+ if(*ref)
+ *ref = _props_reg(props, forge, frames, impl, sequence_num);
+ if(def->event_cb && (def->event_mask & PROP_EVENT_REGISTER) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_REGISTER, impl);
+ }
+
+ if(*ref)
+ *ref = _props_get(props, forge, frames, impl, sequence_num);
+ if(def->event_cb && (def->event_mask & PROP_EVENT_GET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_GET, impl);
+ }
+
+ return 1;
+ }
+ else if(property->atom.type == props->urid.atom_urid)
+ {
+ props_impl_t *impl = _props_impl_search(props, property->body);
+
+ if(impl)
+ {
+ *ref = _props_get(props, forge, frames, impl, sequence_num);
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_GET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_GET, impl);
+
+ return 1;
+ }
+ else if(sequence_num)
+ {
+ *ref = _props_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(sequence_num)
+ {
+ *ref = _props_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(obj->body.otype == props->urid.patch_set)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_URID *property = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+ const LV2_Atom *value = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { props->urid.patch_subject, (const LV2_Atom **)&subject },
+ { props->urid.patch_property, (const LV2_Atom **)&property },
+ { props->urid.patch_sequence, (const LV2_Atom **)&sequence },
+ { props->urid.patch_value, &value },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+ lv2_atom_object_query(obj, q);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(!property || (property->atom.type != props->urid.atom_urid) || !value)
+ {
+ if(sequence_num)
+ {
+ *ref = _props_error(props, forge, frames, sequence_num);
+ }
+
+ return 0;
+ }
+
+ props_impl_t *impl = _props_impl_search(props, property->body);
+ if(impl && (impl->access == props->urid.patch_writable) )
+ {
+ _props_set(props, impl, value->type, value->size, LV2_ATOM_BODY_CONST(value));
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_SET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_SET, impl);
+
+ if(sequence_num)
+ {
+ *ref = _props_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+ else if(sequence_num)
+ {
+ *ref = _props_error(props, forge, frames, sequence_num);
+ }
+ }
+ else if(obj->body.otype == props->urid.patch_put)
+ {
+ const LV2_Atom_URID *subject = NULL;
+ const LV2_Atom_Int *sequence = NULL;
+ const LV2_Atom_Object *body = NULL;
+
+ LV2_Atom_Object_Query q [] = {
+ { props->urid.patch_subject, (const LV2_Atom **)&subject },
+ { props->urid.patch_sequence, (const LV2_Atom **)&sequence},
+ { props->urid.patch_body, (const LV2_Atom **)&body },
+ LV2_ATOM_OBJECT_QUERY_END
+ };
+ lv2_atom_object_query(obj, q);
+
+ // check for a matching optional subject
+ if( (subject && props->urid.subject)
+ && ( (subject->atom.type != props->urid.atom_urid)
+ || (subject->body != props->urid.subject) ) )
+ {
+ return 0;
+ }
+
+ int32_t sequence_num = 0;
+ if(sequence && (sequence->atom.type == props->urid.atom_int))
+ {
+ sequence_num = sequence->body;
+ }
+
+ if(!body || !lv2_atom_forge_is_object_type(forge, body->atom.type))
+ {
+ if(sequence_num)
+ {
+ *ref = _props_error(props, forge, frames, sequence_num);
+ }
+
+ return 0;
+ }
+
+ LV2_ATOM_OBJECT_FOREACH(body, prop)
+ {
+ const LV2_URID property = prop->key;
+ const LV2_Atom *value = &prop->value;
+
+ props_impl_t *impl = _props_impl_search(props, property);
+ if(impl && (impl->access == props->urid.patch_writable) )
+ {
+ _props_set(props, impl, value->type, value->size, LV2_ATOM_BODY_CONST(value));
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_SET) )
+ def->event_cb(props->data, forge, frames, PROP_EVENT_SET, impl);
+ }
+ }
+
+ if(sequence_num)
+ {
+ *ref = _props_ack(props, forge, frames, sequence_num);
+ }
+
+ return 1;
+ }
+
+ return 0; // did not handle a patch event
+}
+
+static inline void
+props_set(props_t *props, LV2_Atom_Forge *forge, uint32_t frames, LV2_URID property,
+ LV2_Atom_Forge_Ref *ref)
+{
+ props_impl_t *impl = _props_impl_search(props, property);
+
+ if(impl)
+ {
+ _props_stash(props, impl);
+ if(*ref)
+ *ref = _props_get(props, forge, frames, impl, 0); //TODO use patch:sequenceNumber
+ }
+}
+
+static inline void
+props_stash(props_t *props, LV2_URID property)
+{
+ props_impl_t *impl = _props_impl_search(props, property);
+
+ if(impl)
+ _props_stash(props, impl);
+}
+
+static inline LV2_State_Status
+props_save(props_t *props, LV2_Atom_Forge *forge, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+
+ // set POD flag if not already set by host
+ flags |= LV2_STATE_IS_POD;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ {
+ map_path = features[i]->data;
+ break;
+ }
+ }
+
+ void *value = malloc(props->max_size); // create memory to store widest value
+ if(value)
+ {
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->access == props->urid.patch_readable)
+ continue; // skip read-only, as it makes no sense to restore them
+
+ // create lockfree copy of value, store() may well be blocking
+ _impl_spin_lock(impl);
+
+ const uint32_t size = _impl_size_get(impl);
+ memcpy(value, impl->stash, size);
+
+ _impl_unlock(impl);
+
+ if( map_path && (impl->type->urid == forge->Path) )
+ {
+ const char *path = strstr(value, "file://")
+ ? value + 7 // skip "file://"
+ : value;
+ char *abstract = map_path->abstract_path(map_path->handle, path);
+ if(abstract && strcmp(abstract, path))
+ {
+ store(state, impl->property, abstract, strlen(abstract) + 1, impl->type->urid, flags);
+ free(abstract);
+ }
+ }
+ else // !Path
+ {
+ store(state, impl->property, value, size, impl->type->urid, flags);
+ }
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_SAVE) )
+ def->event_cb(props->data, forge, 0, PROP_EVENT_SAVE, impl);
+ }
+
+ free(value);
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static inline LV2_State_Status
+props_restore(props_t *props, LV2_Atom_Forge *forge, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags, const LV2_Feature *const *features)
+{
+ const LV2_State_Map_Path *map_path = NULL;
+
+ for(unsigned i = 0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_STATE__mapPath))
+ map_path = features[i]->data;
+ }
+
+ for(unsigned i = 0; i < props->nimpls; i++)
+ {
+ props_impl_t *impl = &props->impls[i];
+
+ if(impl->access == props->urid.patch_readable)
+ continue; // skip read-only, as it makes no sense to restore them
+
+ size_t size;
+ uint32_t type;
+ uint32_t _flags;
+ const void *value = retrieve(state, impl->property, &size, &type, &_flags);
+
+ if(value)
+ {
+ if( map_path && (impl->type->urid == forge->Path) )
+ {
+ char *absolute = map_path->absolute_path(map_path->handle, value);
+ if(absolute)
+ {
+ _props_set(props, impl, type, strlen(absolute) + 1, absolute);
+ free(absolute);
+ }
+ }
+ else // !Path
+ {
+ _props_set(props, impl, type, size, value);
+ }
+
+ const props_def_t *def = impl->def;
+ if(def->event_cb && (def->event_mask & PROP_EVENT_RESTORE) )
+ def->event_cb(props->data, forge, 0, PROP_EVENT_RESTORE, impl);
+ }
+ else
+ {
+ fprintf(stderr, "props_restore: no property '%s'.\n", impl->def->property);
+ }
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+// undefinitions
+#undef PROPS_TYPE_N
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _LV2_PROPS_H_
diff --git a/props.lv2/test/chunk.bin b/props.lv2/test/chunk.bin
new file mode 100644
index 0000000..b66efb8
--- /dev/null
+++ b/props.lv2/test/chunk.bin
Binary files differ
diff --git a/props.lv2/test/manifest.ttl.in b/props.lv2/test/manifest.ttl.in
new file mode 100644
index 0000000..c477aeb
--- /dev/null
+++ b/props.lv2/test/manifest.ttl.in
@@ -0,0 +1,28 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+@prefix props: <http://open-music-kontrollers.ch/lv2/props#> .
+
+# Orbit Looper
+props:test
+ a lv2:Plugin ;
+ lv2:minorVersion @PROPS_MINOR_VERSION@ ;
+ lv2:microVersion @PROPS_MICRO_VERSION@ ;
+ lv2:binary <props@LIB_EXT@> ;
+ rdfs:seeAlso <props.ttl> .
diff --git a/props.lv2/test/props.c b/props.lv2/test/props.c
new file mode 100644
index 0000000..516d48c
--- /dev/null
+++ b/props.lv2/test/props.c
@@ -0,0 +1,580 @@
+/*
+ * Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+
+#include <props.h>
+
+#include <lv2/lv2plug.in/ns/ext/log/log.h>
+
+#define PROPS_PREFIX "http://open-music-kontrollers.ch/lv2/props#"
+#define PROPS_TEST_URI PROPS_PREFIX"test"
+
+#define MAX_NPROPS 33
+#define MAX_STRLEN 256
+
+typedef struct _plugstate0_t plugstate0_t;
+typedef struct _plugstate1_t plugstate1_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate0_t {
+ int32_t val1;
+ int64_t val2;
+ float val3;
+ double val4;
+ int32_t val5;
+ int32_t val6;
+ char val7 [MAX_STRLEN];
+};
+
+struct _plugstate1_t {
+ int32_t val1;
+ int64_t val2;
+ float val3;
+ double val4;
+ char val5 [MAX_STRLEN];
+ char val6 [MAX_STRLEN];
+ uint8_t val7 [MAX_STRLEN];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+ LV2_Log_Log *log;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Ref ref;
+
+ LV2_URID log_trace;
+ LV2_URID log_note;
+
+ PROPS_T(props, MAX_NPROPS);
+
+ plugstate0_t dyn;
+ plugstate0_t _dyn;
+
+ plugstate1_t stat;
+ plugstate1_t _stat;
+
+ struct {
+ LV2_URID stat2;
+ LV2_URID stat4;
+ LV2_URID dyn2;
+ LV2_URID dyn4;
+ } urid;
+
+ const LV2_Atom_Sequence *event_in;
+ LV2_Atom_Sequence *event_out;
+};
+
+static int
+_log_vprintf(plughandle_t *handle, LV2_URID type, const char *fmt, va_list args)
+{
+ return handle->log->vprintf(handle->log->handle, type, fmt, args);
+}
+
+// non-rt || rt with LV2_LOG__Trace
+static int
+_log_printf(plughandle_t *handle, LV2_URID type, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start (args, fmt);
+ ret = _log_vprintf(handle, type, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+static void
+_intercept(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ switch(event)
+ {
+ case PROP_EVENT_GET:
+ {
+ _log_printf(handle, handle->log_trace, "GET : %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_SET:
+ {
+ _log_printf(handle, handle->log_trace, "SET : %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_REGISTER:
+ {
+ _log_printf(handle, handle->log_trace, "REGISTER: %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_SAVE:
+ {
+ _log_printf(handle, handle->log_note, "SAVE : %s", impl->def->label);
+ break;
+ }
+ case PROP_EVENT_RESTORE:
+ {
+ _log_printf(handle, handle->log_note, "RESTORE : %s", impl->def->label);
+ break;
+ }
+ }
+}
+
+static void
+_intercept_dyn1(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->dyn.val2 = handle->dyn.val1 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.dyn2, &handle->ref);
+ }
+}
+
+static void
+_intercept_dyn3(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->dyn.val4 = handle->dyn.val3 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.dyn4, &handle->ref);
+ }
+}
+
+static void
+_intercept_stat1(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->stat.val2 = handle->stat.val1 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.stat2, &handle->ref);
+ }
+}
+
+static void
+_intercept_stat3(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ handle->stat.val4 = handle->stat.val3 * 2;
+
+ props_set(&handle->props, forge, frames, handle->urid.stat4, &handle->ref);
+ }
+}
+
+static void
+_intercept_stat6(void *data, LV2_Atom_Forge *forge, int64_t frames,
+ props_event_t event, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ _intercept(data, forge, frames, event, impl);
+
+ if(event & PROP_EVENT_WRITE)
+ {
+ const char *path = strstr(handle->stat.val6, "file://")
+ ? handle->stat.val6 + 7 // skip "file://"
+ : handle->stat.val6;
+ FILE *f = fopen(path, "wb"); // create empty file
+ if(f)
+ fclose(f);
+ }
+}
+
+static const props_def_t dyn1 = {
+ .label = "Int",
+ .comment = "This is a 32-bit integer",
+ .property = PROPS_PREFIX"Int",
+ .access = LV2_PATCH__writable,
+ .unit = LV2_UNITS__hz,
+ .type = LV2_ATOM__Int,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_dyn1,
+ .minimum.i = 0,
+ .maximum.i = 10
+};
+
+static const props_def_t dyn2 = {
+ .label = "Long",
+ .comment = "This is a 64-bit integer",
+ .property = PROPS_PREFIX"Long",
+ .access = LV2_PATCH__readable,
+ .unit = LV2_UNITS__khz,
+ .type = LV2_ATOM__Long,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.h = 0,
+ .maximum.h = 20
+};
+
+static const props_def_t dyn3 = {
+ .label = "Float",
+ .comment = "This is a 32-bit floating point",
+ .property = PROPS_PREFIX"Float",
+ .access = LV2_PATCH__writable,
+ .unit = LV2_UNITS__mhz,
+ .type = LV2_ATOM__Float,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_dyn3,
+ .minimum.f = -0.5f,
+ .maximum.f = 0.5f
+};
+
+static const props_def_t dyn4 = {
+ .label = "Double",
+ .comment = "This is a 64-bit floating point",
+ .property = PROPS_PREFIX"Double",
+ .access = LV2_PATCH__readable,
+ .unit = LV2_UNITS__db,
+ .type = LV2_ATOM__Double,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.d = -1.0,
+ .maximum.d = 1.0
+};
+
+static const props_scale_point_t scale_points5 [] = {
+ {.label = "One", .value.i = 0},
+ {.label = "Two", .value.i = 1},
+ {.label = "Three", .value.i = 2},
+ {.label = "Four", .value.i = 3},
+ {.label = NULL } // sentinel
+};
+
+static const props_def_t dyn5 = {
+ .label = "scaleInt",
+ .comment = "This is a 32-bit integer enumeration",
+ .property = PROPS_PREFIX"scaleInt",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Int,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.i = 0,
+ .maximum.i = 3,
+ .scale_points = scale_points5
+};
+
+static const props_def_t dyn6 = {
+ .label = "Bool",
+ .comment = "This is a boolean",
+ .property = PROPS_PREFIX"Bool",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Bool,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .minimum.d = 0,
+ .maximum.d = 1
+};
+
+static const props_def_t dyn7 = {
+ .label = "String",
+ .comment = "This is a string",
+ .property = PROPS_PREFIX"String",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__String,
+ .mode = PROP_MODE_DYNAMIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static const props_def_t stat1 = {
+ .label = "statInt",
+ .property = PROPS_PREFIX"statInt",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Int,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_stat1,
+};
+
+static const props_def_t stat2 = {
+ .label = "statLong",
+ .property = PROPS_PREFIX"statLong",
+ .access = LV2_PATCH__readable,
+ .type = LV2_ATOM__Long,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+};
+
+static const props_def_t stat3 = {
+ .label = "statFloat",
+ .property = PROPS_PREFIX"statFloat",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Float,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_stat3,
+};
+
+static const props_def_t stat4 = {
+ .label = "statDouble",
+ .property = PROPS_PREFIX"statDouble",
+ .access = LV2_PATCH__readable,
+ .type = LV2_ATOM__Double,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+};
+
+static const props_def_t stat5 = {
+ .label = "statString",
+ .property = PROPS_PREFIX"statString",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__String,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static const props_def_t stat6 = {
+ .label = "statPath",
+ .property = PROPS_PREFIX"statPath",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Path,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept_stat6,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static const props_def_t stat7 = {
+ .label = "statChunk",
+ .property = PROPS_PREFIX"statChunk",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Chunk,
+ .mode = PROP_MODE_STATIC,
+ .event_mask = PROP_EVENT_ALL,
+ .event_cb = _intercept,
+ .max_size = MAX_STRLEN // strlen
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+ if(!handle->log)
+ {
+ fprintf(stderr,
+ "%s: Host does not support log:log\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ handle->log_trace = handle->map->map(handle->map->handle, LV2_LOG__Trace);
+ handle->log_note = handle->map->map(handle->map->handle, LV2_LOG__Note);
+
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ if(!props_init(&handle->props, MAX_NPROPS, descriptor->URI, handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ plugstate0_t *dyn = &handle->dyn;
+ plugstate0_t *_dyn = &handle->_dyn;
+ plugstate1_t *stat = &handle->stat;
+ plugstate1_t *_stat = &handle->_stat;
+
+ if( !props_register(&handle->props, &dyn1, &dyn->val1, &_dyn->val1)
+ || !(handle->urid.dyn2 =
+ props_register(&handle->props, &dyn2, &dyn->val2, &_dyn->val2))
+ || !props_register(&handle->props, &dyn3, &dyn->val3, &_dyn->val3)
+ || !(handle->urid.dyn4 =
+ props_register(&handle->props, &dyn4, &dyn->val4, &_dyn->val4))
+ || !props_register(&handle->props, &dyn5, &dyn->val5, &_dyn->val5)
+ || !props_register(&handle->props, &dyn6, &dyn->val6, &_dyn->val6)
+ || !props_register(&handle->props, &dyn7, &dyn->val7, &_dyn->val7)
+
+ || !props_register(&handle->props, &stat1, &stat->val1, &_stat->val1)
+ || !(handle->urid.stat2 =
+ props_register(&handle->props, &stat2, &stat->val2, &_stat->val2))
+ || !props_register(&handle->props, &stat3, &stat->val3, &_stat->val3)
+ || !(handle->urid.stat4 =
+ props_register(&handle->props, &stat4, &stat->val4, &_stat->val4))
+ || !props_register(&handle->props, &stat5, &stat->val5, &_stat->val5)
+ || !props_register(&handle->props, &stat6, &stat->val6, &_stat->val6)
+ || !props_register(&handle->props, &stat7, &stat->val7, &_stat->val7) )
+ {
+ _log_printf(handle, handle->log_trace, "ERR : registering");
+ free(handle);
+ return NULL;
+ }
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->event_in = (const LV2_Atom_Sequence *)data;
+ break;
+ case 1:
+ handle->event_out = (LV2_Atom_Sequence *)data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ uint32_t capacity = handle->event_out->atom.size;
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_buffer(&handle->forge, (uint8_t *)handle->event_out, capacity);
+ handle->ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->event_in, ev)
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+
+ if(handle->ref)
+ props_advance(&handle->props, &handle->forge, ev->time.frames, obj, &handle->ref); //TODO handle return
+ }
+ if(handle->ref)
+ lv2_atom_forge_pop(&handle->forge, &frame);
+ else
+ lv2_atom_sequence_clear(handle->event_out);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_save(&handle->props, &handle->forge, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ return props_restore(&handle->props, &handle->forge, retrieve, state, flags, features);
+}
+
+LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+ return NULL;
+}
+
+const LV2_Descriptor props_test = {
+ .URI = PROPS_TEST_URI,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &props_test;
+ default:
+ return NULL;
+ }
+}
diff --git a/props.lv2/test/props.ttl b/props.lv2/test/props.ttl
new file mode 100644
index 0000000..282710b
--- /dev/null
+++ b/props.lv2/test/props.ttl
@@ -0,0 +1,152 @@
+# Copyright (c) 2015 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix log: <http://lv2plug.in/ns/ext/log#> .
+@prefix units: <http://lv2plug.in/ns/extensions/units#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix props: <http://open-music-kontrollers.ch/lv2/props#> .
+
+# Maintainer
+omk:me
+ a foaf:Person ;
+ foaf:name "Hanspeter Portner" ;
+ foaf:mbox <mailto:dev@open-music-kontrollers.ch> ;
+ foaf:homepage <http://open-music-kontrollers.ch> .
+
+# Project
+proj:props
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Props Bundle" .
+
+props:statInt
+ a lv2:Parameter ;
+ rdfs:range atom:Int ;
+ rdfs:label "statInt" ;
+ rdfs:comment "This is a 32-bit integer" ;
+ units:unit units:hz ;
+ lv2:minimum 0 ;
+ lv2:maximum 10 .
+
+props:statLong
+ a lv2:Parameter ;
+ rdfs:range atom:Long ;
+ rdfs:label "statLong" ;
+ rdfs:comment "This is a 64-bit integer" ;
+ units:unit units:khz ;
+ lv2:minimum 0 ;
+ lv2:maximum 20 .
+
+props:statFloat
+ a lv2:Parameter ;
+ rdfs:range atom:Float ;
+ rdfs:label "statFloat" ;
+ rdfs:comment "This is a 32-bit float" ;
+ units:unit units:mhz ;
+ lv2:minimum -0.5 ;
+ lv2:maximum 0.5 .
+
+props:statDouble
+ a lv2:Parameter ;
+ rdfs:range atom:Double ;
+ rdfs:label "statDouble" ;
+ rdfs:comment "This is a 64-bit double" ;
+ units:unit units:db ;
+ lv2:minimum -1.0 ;
+ lv2:maximum 1.0 .
+
+props:statString
+ a lv2:Parameter ;
+ rdfs:range atom:String ;
+ rdfs:label "statString" ;
+ rdfs:comment "This is a string" .
+
+props:statPath
+ a lv2:Parameter ;
+ rdfs:range atom:Path ;
+ rdfs:label "statPath" ;
+ rdfs:comment "This is a path" .
+
+props:statChunk
+ a lv2:Parameter ;
+ rdfs:range atom:Chunk;
+ rdfs:label "statChunk" ;
+ rdfs:comment "This is a chunk" .
+
+# Looper Test
+props:test
+ a lv2:Plugin ,
+ lv2:ConverterPlugin ;
+ doap:name "Props Test" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:props ;
+ lv2:extensionData state:interface ;
+ lv2:requiredFeature urid:map, log:log, state:loadDefaultState ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+
+ lv2:port [
+ # sink event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 0 ;
+ lv2:symbol "event_in" ;
+ lv2:name "Event Input" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # source event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "event_out" ;
+ lv2:name "Event Output" ;
+ lv2:designation lv2:control ;
+ ] ;
+
+ patch:writable
+ props:statInt ,
+ props:statFloat ,
+ props:statString ,
+ props:statPath ,
+ props:statChunk ;
+
+ patch:readable
+ props:statLong ,
+ props:statDouble ;
+
+ state:state [
+ props:statInt 4 ;
+ props:statFloat "0.4"^^xsd:float ;
+ props:statString "Hello world" ;
+ props:statPath <manifest.ttl> ;
+ props:statChunk "AQIDBAUGBw=="^^xsd:base64Binary ;
+ ] .
diff --git a/pugl/.gitignore b/pugl/.gitignore
new file mode 100644
index 0000000..e511642
--- /dev/null
+++ b/pugl/.gitignore
@@ -0,0 +1,3 @@
+.waf*/
+build/
+.lock-waf*
diff --git a/pugl/AUTHORS b/pugl/AUTHORS
new file mode 100644
index 0000000..5625baa
--- /dev/null
+++ b/pugl/AUTHORS
@@ -0,0 +1,11 @@
+Author:
+ David Robillard <d@drobilla.net>
+
+Original GLX inspiration, portability fixes:
+ Ben Loftis
+
+Various fixes and improvements:
+ Robin Gareus <robin@gareus.org>
+
+UTF-8 and Windows event work:
+ Erik Åldstedt Sund <erikalds@gmail.com>
diff --git a/pugl/COPYING b/pugl/COPYING
new file mode 100644
index 0000000..e1e203d
--- /dev/null
+++ b/pugl/COPYING
@@ -0,0 +1,13 @@
+Copyright 2011-2014 David Robillard <http://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
+copyright notice and this permission notice appear in all copies.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/pugl/Doxyfile.in b/pugl/Doxyfile.in
new file mode 100644
index 0000000..59e3331
--- /dev/null
+++ b/pugl/Doxyfile.in
@@ -0,0 +1,2415 @@
+# Doxyfile 1.8.12
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = Pugl
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER = @PUGL_VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = NO
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = NO
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = YES
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = YES
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = YES
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = YES
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = YES
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = YES
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = NO
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = NO
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = NO
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = NO
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = YES
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = pugl
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS = *.c
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = NO
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 130
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 30
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 100
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = YES
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = YES
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 1
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = NO
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME =
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = NO
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = NO
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = NO
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = NO
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = NO
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = NO
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = NO
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/pugl/README.md b/pugl/README.md
new file mode 100644
index 0000000..77809d8
--- /dev/null
+++ b/pugl/README.md
@@ -0,0 +1,28 @@
+PUGL
+====
+
+Pugl is a minimal portable API for GUIs which supports embedding and is
+suitable for use in plugins. It works on X11, Mac OS X, and Windows. GUIs can
+be drawn with OpenGL or Cairo.
+
+Pugl is vaguely similar to GLUT, but with some significant distinctions:
+
+ * Minimal in scope, providing only what is necessary to draw and receive
+ keyboard and mouse input.
+
+ * No reliance on static data whatsoever, so the API can be used in plugins or
+ multiple independent parts of a program.
+
+ * Single implementation, which is small, liberally licensed Free / Open Source
+ Software, and suitable for direct inclusion in programs if avoiding a
+ library dependency is desired.
+
+ * Support for embedding in other windows, so Pugl code can draw to a widget
+ inside a larger GUI.
+
+ * More complete support for keyboard input, including additional "special"
+ keys, modifiers, and support for detecting individual modifier key presses.
+
+For more information, see <http://drobilla.net/software/pugl>.
+
+ -- David Robillard <d@drobilla.net>
diff --git a/pugl/pugl.pc.in b/pugl/pugl.pc.in
new file mode 100644
index 0000000..d3606cd
--- /dev/null
+++ b/pugl/pugl.pc.in
@@ -0,0 +1,10 @@
+prefix=@PREFIX@
+exec_prefix=@EXEC_PREFIX@
+libdir=@LIBDIR@
+includedir=@INCLUDEDIR@
+
+Name: Pugl
+Version: @PUGL_VERSION@
+Description: Lightweight portable OpenGL API
+Libs: -L${libdir} -l@LIB_PUGL@
+Cflags: -I${includedir}/pugl-@PUGL_MAJOR_VERSION@
diff --git a/pugl/pugl/cairo_gl.h b/pugl/pugl/cairo_gl.h
new file mode 100644
index 0000000..5c0f1f9
--- /dev/null
+++ b/pugl/pugl/cairo_gl.h
@@ -0,0 +1,105 @@
+/*
+ Copyright 2016 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#if defined(PUGL_HAVE_GL) && defined(PUGL_HAVE_CAIRO)
+
+#include <cairo/cairo.h>
+#include <stdint.h>
+
+#include "pugl/gl.h"
+
+typedef struct {
+ unsigned texture_id;
+ uint8_t* buffer;
+} PuglCairoGL;
+
+static cairo_surface_t*
+pugl_cairo_gl_create(PuglCairoGL* ctx, int width, int height, int bpp)
+{
+ free(ctx->buffer);
+ ctx->buffer = (uint8_t*)calloc(bpp * width * height, sizeof(uint8_t));
+ if (!ctx->buffer) {
+ fprintf(stderr, "failed to allocate surface buffer\n");
+ return NULL;
+ }
+
+ return cairo_image_surface_create_for_data(
+ ctx->buffer, CAIRO_FORMAT_ARGB32, width, height, bpp * width);
+}
+
+static void
+pugl_cairo_gl_free(PuglCairoGL* ctx)
+{
+ free(ctx->buffer);
+ ctx->buffer = NULL;
+}
+
+static void
+pugl_cairo_gl_configure(PuglCairoGL* ctx, int width, int height)
+{
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_TEXTURE_RECTANGLE_ARB);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glDeleteTextures(1, &ctx->texture_id);
+ glGenTextures(1, &ctx->texture_id);
+ glBindTexture(GL_TEXTURE_RECTANGLE_ARB, ctx->texture_id);
+ glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
+}
+
+static void
+pugl_cairo_gl_draw(PuglCairoGL* ctx, int width, int height)
+{
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glViewport(0, 0, width, height);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glPushMatrix();
+ glEnable(GL_TEXTURE_RECTANGLE_ARB);
+ glEnable(GL_TEXTURE_2D);
+
+ glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8,
+ width, height, 0,
+ GL_BGRA, GL_UNSIGNED_BYTE, ctx->buffer);
+
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0f, (GLfloat)height);
+ glVertex2f(-1.0f, -1.0f);
+
+ glTexCoord2f((GLfloat)width, (GLfloat)height);
+ glVertex2f(1.0f, -1.0f);
+
+ glTexCoord2f((GLfloat)width, 0.0f);
+ glVertex2f(1.0f, 1.0f);
+
+ glTexCoord2f(0.0f, 0.0f);
+ glVertex2f(-1.0f, 1.0f);
+ glEnd();
+
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_TEXTURE_RECTANGLE_ARB);
+ glPopMatrix();
+}
+
+#endif
diff --git a/pugl/pugl/gl.h b/pugl/pugl/gl.h
new file mode 100644
index 0000000..9a6aeef
--- /dev/null
+++ b/pugl/pugl/gl.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2012-2014 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file gl.h Portable header wrapper for gl.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+#ifdef __APPLE__
+# include "OpenGL/gl.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/gl.h"
+#endif
+
diff --git a/pugl/pugl/glew.h b/pugl/pugl/glew.h
new file mode 100644
index 0000000..5374406
--- /dev/null
+++ b/pugl/pugl/glew.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2016 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file gl.h Portable header wrapper for glew.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+#ifdef __APPLE__
+# include "OpenGL/glew.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/glew.h"
+#endif
+
diff --git a/pugl/pugl/glu.h b/pugl/pugl/glu.h
new file mode 100644
index 0000000..0d3e8e1
--- /dev/null
+++ b/pugl/pugl/glu.h
@@ -0,0 +1,32 @@
+/*
+ Copyright 2012-2015 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file gl.h Portable header wrapper for glu.h.
+
+ Unfortunately, GL includes vary across platforms so this header allows for
+ pure portable programs.
+*/
+
+#ifdef __APPLE__
+# include "OpenGL/glu.h"
+#else
+# ifdef _WIN32
+# include <windows.h> /* Broken Windows GL headers require this */
+# endif
+# include "GL/glu.h"
+#endif
+
diff --git a/pugl/pugl/pugl.h b/pugl/pugl/pugl.h
new file mode 100644
index 0000000..1b22260
--- /dev/null
+++ b/pugl/pugl/pugl.h
@@ -0,0 +1,622 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl.h API for Pugl, a minimal portable API for OpenGL.
+*/
+
+#ifndef PUGL_H_INCLUDED
+#define PUGL_H_INCLUDED
+
+#include <stdint.h>
+
+#ifdef PUGL_SHARED
+# ifdef _WIN32
+# define PUGL_LIB_IMPORT __declspec(dllimport)
+# define PUGL_LIB_EXPORT __declspec(dllexport)
+# else
+# define PUGL_LIB_IMPORT __attribute__((visibility("default")))
+# define PUGL_LIB_EXPORT __attribute__((visibility("default")))
+# endif
+# ifdef PUGL_INTERNAL
+# define PUGL_API PUGL_LIB_EXPORT
+# else
+# define PUGL_API PUGL_LIB_IMPORT
+# endif
+#else
+# define PUGL_API
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#else
+# include <stdbool.h>
+#endif
+
+/**
+ @defgroup pugl Pugl
+ A minimal portable API for OpenGL.
+ @{
+*/
+
+/**
+ A Pugl view.
+*/
+typedef struct PuglViewImpl PuglView;
+
+/**
+ A native window handle.
+
+ On X11, this is a Window.
+ On OSX, this is an NSView*.
+ On Windows, this is a HWND.
+*/
+typedef intptr_t PuglNativeWindow;
+
+/**
+ Handle for opaque user data.
+*/
+typedef void* PuglHandle;
+
+/**
+ Return status code.
+*/
+typedef enum {
+ PUGL_SUCCESS = 0
+} PuglStatus;
+
+/**
+ Drawing context type.
+*/
+typedef enum {
+ PUGL_GL = 0x1,
+ PUGL_CAIRO = 0x2,
+ PUGL_CAIRO_GL = 0x3
+} PuglContextType;
+
+/**
+ Convenience symbols for ASCII control characters.
+*/
+typedef enum {
+ PUGL_CHAR_BACKSPACE = 0x08,
+ PUGL_CHAR_ESCAPE = 0x1B,
+ PUGL_CHAR_DELETE = 0x7F
+} PuglChar;
+
+/**
+ Keyboard modifier flags.
+*/
+typedef enum {
+ PUGL_MOD_SHIFT = 1, /**< Shift key */
+ PUGL_MOD_CTRL = 1 << 1, /**< Control key */
+ PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */
+ PUGL_MOD_SUPER = 1 << 3 /**< Mod4/Command/Windows key */
+} PuglMod;
+
+/**
+ Special (non-Unicode) keyboard keys.
+
+ The numerical values of these symbols occupy a reserved range of Unicode
+ points, so it is possible to express either a PuglKey value or a Unicode
+ character in the same variable. This is sometimes useful for interfacing
+ with APIs that do not make this distinction.
+*/
+typedef enum {
+ PUGL_KEY_F1 = 0xE000,
+ PUGL_KEY_F2,
+ PUGL_KEY_F3,
+ PUGL_KEY_F4,
+ PUGL_KEY_F5,
+ PUGL_KEY_F6,
+ PUGL_KEY_F7,
+ PUGL_KEY_F8,
+ PUGL_KEY_F9,
+ PUGL_KEY_F10,
+ PUGL_KEY_F11,
+ PUGL_KEY_F12,
+ PUGL_KEY_LEFT,
+ PUGL_KEY_UP,
+ PUGL_KEY_RIGHT,
+ PUGL_KEY_DOWN,
+ PUGL_KEY_PAGE_UP,
+ PUGL_KEY_PAGE_DOWN,
+ PUGL_KEY_HOME,
+ PUGL_KEY_END,
+ PUGL_KEY_INSERT,
+ PUGL_KEY_SHIFT,
+ PUGL_KEY_CTRL,
+ PUGL_KEY_ALT,
+ PUGL_KEY_SUPER
+} PuglKey;
+
+/**
+ The type of a PuglEvent.
+*/
+typedef enum {
+ PUGL_NOTHING, /**< No event */
+ PUGL_BUTTON_PRESS, /**< Mouse button press */
+ PUGL_BUTTON_RELEASE, /**< Mouse button release */
+ PUGL_CONFIGURE, /**< View moved and/or resized */
+ PUGL_EXPOSE, /**< View exposed, redraw required */
+ PUGL_CLOSE, /**< Close view */
+ PUGL_KEY_PRESS, /**< Key press */
+ PUGL_KEY_RELEASE, /**< Key release */
+ PUGL_ENTER_NOTIFY, /**< Pointer entered view */
+ PUGL_LEAVE_NOTIFY, /**< Pointer left view */
+ PUGL_MOTION_NOTIFY, /**< Pointer motion */
+ PUGL_SCROLL, /**< Scroll */
+ PUGL_FOCUS_IN, /**< Keyboard focus entered view */
+ PUGL_FOCUS_OUT /**< Keyboard focus left view */
+} PuglEventType;
+
+typedef enum {
+ PUGL_IS_SEND_EVENT = 1
+} PuglEventFlag;
+
+/**
+ Reason for a PuglEventCrossing.
+*/
+typedef enum {
+ PUGL_CROSSING_NORMAL, /**< Crossing due to pointer motion. */
+ PUGL_CROSSING_GRAB, /**< Crossing due to a grab. */
+ PUGL_CROSSING_UNGRAB /**< Crossing due to a grab release. */
+} PuglCrossingMode;
+
+/**
+ Common header for all event structs.
+*/
+typedef struct {
+ PuglEventType type; /**< Event type. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+} PuglEventAny;
+
+/**
+ Button press or release event.
+
+ For event types PUGL_BUTTON_PRESS and PUGL_BUTTON_RELEASE.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_BUTTON_PRESS or PUGL_BUTTON_RELEASE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ unsigned button; /**< 1-relative button number. */
+} PuglEventButton;
+
+/**
+ Configure event for when window size or position has changed.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_CONFIGURE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ double x; /**< New parent-relative X coordinate. */
+ double y; /**< New parent-relative Y coordinate. */
+ double width; /**< New width. */
+ double height; /**< New height. */
+} PuglEventConfigure;
+
+/**
+ Expose event for when a region must be redrawn.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_EXPOSE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double width; /**< Width of exposed region. */
+ double height; /**< Height of exposed region. */
+ int count; /**< Number of expose events to follow. */
+} PuglEventExpose;
+
+/**
+ Window close event.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_CLOSE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+} PuglEventClose;
+
+/**
+ Key press/release event.
+
+ Keys that correspond to a Unicode character have `character` and `utf8` set.
+ Other keys will have `character` 0, but `special` may be set if this is a
+ known special key.
+
+ A key press may be part of a multi-key sequence to generate a wide
+ character. If `filter` is set, this event is part of a multi-key sequence
+ and should be ignored if the application is reading textual input.
+ Following the series of filtered press events, a press event with
+ `character` and `utf8` (but `keycode` 0) will be sent. This event will have
+ no corresponding release event.
+
+ Generally, an application should either work with raw keyboard press/release
+ events based on `keycode` (ignoring events with `keycode` 0), or
+ read textual input based on `character` or `utf8` (ignoring releases and
+ events with `filter` 1). Note that blindly appending `utf8` will yield
+ incorrect text, since press events are sent for both individually composed
+ keys and the resulting synthetic multi-byte press.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_KEY_PRESS or PUGL_KEY_RELEASE. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ unsigned keycode; /**< Raw key code. */
+ uint32_t character; /**< Unicode character code, or 0. */
+ PuglKey special; /**< Special key, or 0. */
+ uint8_t utf8[8]; /**< UTF-8 string. */
+ bool filter; /**< True if part of a multi-key sequence. */
+} PuglEventKey;
+
+/**
+ Pointer crossing event (enter and leave).
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_ENTER_NOTIFY or PUGL_LEAVE_NOTIFY. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ PuglCrossingMode mode; /**< Reason for crossing. */
+} PuglEventCrossing;
+
+/**
+ Pointer motion event.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_MOTION_NOTIFY. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ bool is_hint; /**< True iff this event is a motion hint. */
+ bool focus; /**< True iff this is the focused window. */
+} PuglEventMotion;
+
+/**
+ Scroll event.
+
+ The scroll distance is expressed in "lines", an arbitrary unit that
+ corresponds to a single tick of a detented mouse wheel. For example, `dy` =
+ 1.0 scrolls 1 line up. Some systems and devices support finer resolution
+ and/or higher values for fast scrolls, so programs should handle any value
+ gracefully.
+ */
+typedef struct {
+ PuglEventType type; /**< PUGL_SCROLL. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ uint32_t time; /**< Time in milliseconds. */
+ double x; /**< View-relative X coordinate. */
+ double y; /**< View-relative Y coordinate. */
+ double x_root; /**< Root-relative X coordinate. */
+ double y_root; /**< Root-relative Y coordinate. */
+ unsigned state; /**< Bitwise OR of PuglMod flags. */
+ double dx; /**< Scroll X distance in lines. */
+ double dy; /**< Scroll Y distance in lines. */
+} PuglEventScroll;
+
+/**
+ Keyboard focus event.
+*/
+typedef struct {
+ PuglEventType type; /**< PUGL_FOCUS_IN or PUGL_FOCUS_OUT. */
+ PuglView* view; /**< View that received this event. */
+ uint32_t flags; /**< Bitwise OR of PuglEventFlag values. */
+ bool grab; /**< True iff this is a grab/ungrab event. */
+} PuglEventFocus;
+
+/**
+ Interface event.
+
+ This is a union of all event structs. The `type` must be checked to
+ determine which fields are safe to access. A pointer to PuglEvent can
+ either be cast to the appropriate type, or the union members used.
+*/
+typedef union {
+ PuglEventType type; /**< Event type. */
+ PuglEventAny any; /**< Valid for all event types. */
+ PuglEventButton button; /**< PUGL_BUTTON_PRESS, PUGL_BUTTON_RELEASE. */
+ PuglEventConfigure configure; /**< PUGL_CONFIGURE. */
+ PuglEventExpose expose; /**< PUGL_EXPOSE. */
+ PuglEventClose close; /**< PUGL_CLOSE. */
+ PuglEventKey key; /**< PUGL_KEY_PRESS, PUGL_KEY_RELEASE. */
+ PuglEventCrossing crossing; /**< PUGL_ENTER_NOTIFY, PUGL_LEAVE_NOTIFY. */
+ PuglEventMotion motion; /**< PUGL_MOTION_NOTIFY. */
+ PuglEventScroll scroll; /**< PUGL_SCROLL. */
+ PuglEventFocus focus; /**< PUGL_FOCUS_IN, PUGL_FOCUS_OUT. */
+} PuglEvent;
+
+/**
+ @name Initialization
+ Configuration functions which must be called before creating a window.
+ @{
+*/
+
+/**
+ Create a Pugl view.
+
+ To create a window, call the various puglInit* functions as necessary, then
+ call puglCreateWindow().
+
+ @param pargc Pointer to argument count (currently unused).
+ @param argv Arguments (currently unused).
+ @return A newly created view.
+*/
+PUGL_API PuglView*
+puglInit(int* pargc, char** argv);
+
+/**
+ Set the window class name before creating a window.
+*/
+PUGL_API void
+puglInitWindowClass(PuglView* view, const char* name);
+
+/**
+ Set the parent window before creating a window (for embedding).
+*/
+PUGL_API void
+puglInitWindowParent(PuglView* view, PuglNativeWindow parent);
+
+/**
+ Set the window size before creating a window.
+*/
+PUGL_API void
+puglInitWindowSize(PuglView* view, int width, int height);
+
+/**
+ Set the minimum window size before creating a window.
+*/
+PUGL_API void
+puglInitWindowMinSize(PuglView* view, int width, int height);
+
+/**
+ Set the window aspect ratio range before creating a window.
+
+ The x and y values here represent a ratio of width to height. To set a
+ fixed aspect ratio, set the minimum and maximum values to the same ratio.
+*/
+PUGL_API void
+puglInitWindowAspectRatio(PuglView* view,
+ int min_x,
+ int min_y,
+ int max_x,
+ int max_y);
+
+/**
+ Enable or disable resizing before creating a window.
+*/
+PUGL_API void
+puglInitResizable(PuglView* view, bool resizable);
+
+/**
+ Set transient parent before creating a window.
+
+ On X11, parent must be a Window.
+ On OSX, parent must be an NSView*.
+*/
+PUGL_API void
+puglInitTransientFor(PuglView* view, uintptr_t parent);
+
+/**
+ Set the context type before creating a window.
+*/
+PUGL_API void
+puglInitContextType(PuglView* view, PuglContextType type);
+
+/**
+ @}
+*/
+
+/**
+ @name Windows
+ Functions for creating and managing a visible window for a view.
+ @{
+*/
+
+/**
+ Create a window with the settings given by the various puglInit functions.
+
+ @return 1 (pugl does not currently support multiple windows).
+*/
+PUGL_API int
+puglCreateWindow(PuglView* view, const char* title);
+
+/**
+ Show the current window.
+*/
+PUGL_API void
+puglShowWindow(PuglView* view);
+
+/**
+ Hide the current window.
+*/
+PUGL_API void
+puglHideWindow(PuglView* view);
+
+/**
+ Return the native window handle.
+*/
+PUGL_API PuglNativeWindow
+puglGetNativeWindow(PuglView* view);
+
+/**
+ @}
+*/
+
+/**
+ Set the handle to be passed to all callbacks.
+
+ This is generally a pointer to a struct which contains all necessary state.
+ Everything needed in callbacks should be here, not in static variables.
+*/
+PUGL_API void
+puglSetHandle(PuglView* view, PuglHandle handle);
+
+/**
+ Get the handle to be passed to all callbacks.
+*/
+PUGL_API PuglHandle
+puglGetHandle(PuglView* view);
+
+/**
+ Return true iff the view is currently visible.
+*/
+PUGL_API bool
+puglGetVisible(PuglView* view);
+
+/**
+ Get the current size of the view.
+*/
+PUGL_API void
+puglGetSize(PuglView* view, int* width, int* height);
+
+/**
+ @name Context
+ Functions for accessing the drawing context.
+ @{
+*/
+
+/**
+ Get the drawing context.
+
+ For PUGL_GL contexts, this is unused and returns NULL.
+ For PUGL_CAIRO contexts, this returns a pointer to a cairo_t.
+*/
+PUGL_API void*
+puglGetContext(PuglView* view);
+
+
+/**
+ Enter the drawing context.
+
+ This must be called before any code that accesses the drawing context,
+ including any GL functions. This is only necessary for code that does so
+ outside the usual draw callback or handling of an expose event.
+*/
+PUGL_API void
+puglEnterContext(PuglView* view);
+
+/**
+ Leave the drawing context.
+
+ This must be called after puglEnterContext and applies the results of the
+ drawing code (for example, by swapping buffers).
+*/
+PUGL_API void
+puglLeaveContext(PuglView* view, bool flush);
+
+/**
+ @}
+*/
+
+/**
+ @name Event Handling
+ @{
+*/
+
+/**
+ A function called when an event occurs.
+*/
+typedef void (*PuglEventFunc)(PuglView* view, const PuglEvent* event);
+
+/**
+ Set the function to call when an event occurs.
+*/
+PUGL_API void
+puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc);
+
+/**
+ Ignore synthetic repeated key events.
+*/
+PUGL_API void
+puglIgnoreKeyRepeat(PuglView* view, bool ignore);
+
+/**
+ Grab the input focus.
+*/
+PUGL_API void
+puglGrabFocus(PuglView* view);
+
+/**
+ Block and wait for an event to be ready.
+
+ This can be used in a loop to only process events via puglProcessEvents when
+ necessary. This function will block indefinitely if no events are
+ available, so is not appropriate for use in programs that need to perform
+ regular updates (e.g. animation).
+*/
+PUGL_API PuglStatus
+puglWaitForEvent(PuglView* view);
+
+/**
+ Process all pending window events.
+
+ This handles input events as well as rendering, so it should be called
+ regularly and rapidly enough to keep the UI responsive. This function does
+ not block if no events are pending.
+*/
+PUGL_API PuglStatus
+puglProcessEvents(PuglView* view);
+
+/**
+ @}
+*/
+
+/**
+ Request a redisplay on the next call to puglProcessEvents().
+*/
+PUGL_API void
+puglPostRedisplay(PuglView* view);
+
+/**
+ Destroy a GL window.
+*/
+PUGL_API void
+puglDestroy(PuglView* view);
+
+/**
+ @}
+*/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PUGL_H_INCLUDED */
diff --git a/pugl/pugl/pugl.hpp b/pugl/pugl/pugl.hpp
new file mode 100644
index 0000000..8232887
--- /dev/null
+++ b/pugl/pugl/pugl.hpp
@@ -0,0 +1,106 @@
+/*
+ Copyright 2012-2015 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl.hpp C++ API for Pugl, a minimal portable API for OpenGL.
+*/
+
+#ifndef PUGL_HPP_INCLUDED
+#define PUGL_HPP_INCLUDED
+
+#include "pugl/pugl.h"
+
+/**
+ @defgroup puglmm Puglmm
+ C++ API wrapper for Pugl.
+ @{
+*/
+
+namespace pugl {
+
+class View {
+public:
+ View(int* pargc, char** argv)
+ : _view(puglInit(pargc, argv))
+ {
+ puglSetHandle(_view, this);
+ puglSetEventFunc(_view, _onEvent);
+ }
+
+ virtual ~View() { puglDestroy(_view); }
+
+ virtual void initWindowParent(PuglNativeWindow parent) {
+ puglInitWindowParent(_view, parent);
+ }
+
+ virtual void initWindowSize(int width, int height) {
+ puglInitWindowSize(_view, width, height);
+ }
+
+ virtual void initWindowMinSize(int width, int height) {
+ puglInitWindowMinSize(_view, width, height);
+ }
+
+ virtual void initWindowAspectRatio(int min_x, int min_y, int max_x, int max_y) {
+ puglInitWindowAspectRatio(_view, min_x, min_y, max_x, max_y);
+ }
+
+ virtual void initResizable(bool resizable) {
+ puglInitResizable(_view, resizable);
+ }
+
+ virtual void initTransientFor(uintptr_t parent) {
+ puglInitTransientFor(_view, parent);
+ }
+
+ virtual void initContextType(PuglContextType type) {
+ puglInitContextType(_view, type);
+ }
+
+ virtual void createWindow(const char* title) {
+ puglCreateWindow(_view, title);
+ }
+
+ virtual void showWindow() { puglShowWindow(_view); }
+ virtual void hideWindow() { puglHideWindow(_view); }
+ virtual PuglNativeWindow getNativeWindow() { return puglGetNativeWindow(_view); }
+
+ virtual void onEvent(const PuglEvent* event) = 0;
+
+ virtual void* getContext() { return puglGetContext(_view); }
+ virtual void ignoreKeyRepeat(bool ignore) { puglIgnoreKeyRepeat(_view, ignore); }
+ virtual void grabFocus() { puglGrabFocus(_view); }
+ virtual PuglStatus waitForEvent() { return puglWaitForEvent(_view); }
+ virtual PuglStatus processEvents() { return puglProcessEvents(_view); }
+ virtual void postRedisplay() { puglPostRedisplay(_view); }
+
+ PuglView* cobj() { return _view; }
+
+private:
+ static void _onEvent(PuglView* view, const PuglEvent* event) {
+ ((View*)puglGetHandle(view))->onEvent(event);
+ }
+
+ PuglView* _view;
+};
+
+} // namespace pugl
+
+/**
+ @}
+*/
+
+#endif /* PUGL_HPP_INCLUDED */
diff --git a/pugl/pugl/pugl_internal.h b/pugl/pugl/pugl_internal.h
new file mode 100644
index 0000000..4a3fc0c
--- /dev/null
+++ b/pugl/pugl/pugl_internal.h
@@ -0,0 +1,241 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_internal.h Private platform-independent definitions.
+
+ Note this file contains function definitions, so it must be compiled into
+ the final binary exactly once. Each platform specific implementation file
+ including it once should achieve this.
+
+ If you are copying the pugl code into your source tree, the following
+ symbols can be defined to tweak pugl behaviour:
+
+ PUGL_HAVE_CAIRO: Include Cairo support code.
+ PUGL_HAVE_GL: Include OpenGL support code.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "pugl/pugl.h"
+
+typedef struct PuglInternalsImpl PuglInternals;
+
+struct PuglViewImpl {
+ PuglHandle handle;
+ PuglEventFunc eventFunc;
+
+ PuglInternals* impl;
+
+ char* windowClass;
+ PuglNativeWindow parent;
+ PuglContextType ctx_type;
+ uintptr_t transient_parent;
+
+ int width;
+ int height;
+ int min_width;
+ int min_height;
+ int min_aspect_x;
+ int min_aspect_y;
+ int max_aspect_x;
+ int max_aspect_y;
+ bool ignoreKeyRepeat;
+ bool redisplay;
+ bool resizable;
+ bool visible;
+};
+
+PuglInternals* puglInitInternals(void);
+
+PuglView*
+puglInit(int* pargc, char** argv)
+{
+ PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
+ if (!view) {
+ return NULL;
+ }
+
+ PuglInternals* impl = puglInitInternals();
+ if (!impl) {
+ return NULL;
+ }
+
+ view->ctx_type = PUGL_GL;
+ view->impl = impl;
+ view->width = 640;
+ view->height = 480;
+
+ return view;
+}
+
+void
+puglInitWindowSize(PuglView* view, int width, int height)
+{
+ view->width = width;
+ view->height = height;
+}
+
+void
+puglInitWindowMinSize(PuglView* view, int width, int height)
+{
+ view->min_width = width;
+ view->min_height = height;
+}
+
+void
+puglInitWindowAspectRatio(PuglView* view,
+ int min_x,
+ int min_y,
+ int max_x,
+ int max_y)
+{
+ view->min_aspect_x = min_x;
+ view->min_aspect_y = min_y;
+ view->max_aspect_x = max_x;
+ view->max_aspect_y = max_y;
+}
+
+void
+puglInitWindowClass(PuglView* view, const char* name)
+{
+ const size_t len = strlen(name);
+
+ free(view->windowClass);
+ view->windowClass = (char*)calloc(1, len + 1);
+ memcpy(view->windowClass, name, len);
+}
+
+void
+puglInitWindowParent(PuglView* view, PuglNativeWindow parent)
+{
+ view->parent = parent;
+}
+
+void
+puglInitResizable(PuglView* view, bool resizable)
+{
+ view->resizable = resizable;
+}
+
+void
+puglInitTransientFor(PuglView* view, uintptr_t parent)
+{
+ view->transient_parent = parent;
+}
+
+void
+puglInitContextType(PuglView* view, PuglContextType type)
+{
+ view->ctx_type = type;
+}
+
+void
+puglSetHandle(PuglView* view, PuglHandle handle)
+{
+ view->handle = handle;
+}
+
+PuglHandle
+puglGetHandle(PuglView* view)
+{
+ return view->handle;
+}
+
+bool
+puglGetVisible(PuglView* view)
+{
+ return view->visible;
+}
+
+void
+puglGetSize(PuglView* view, int* width, int* height)
+{
+ *width = view->width;
+ *height = view->height;
+}
+
+void
+puglIgnoreKeyRepeat(PuglView* view, bool ignore)
+{
+ view->ignoreKeyRepeat = ignore;
+}
+
+void
+puglSetEventFunc(PuglView* view, PuglEventFunc eventFunc)
+{
+ view->eventFunc = eventFunc;
+}
+
+/** Return the code point for buf, or the replacement character on error. */
+static uint32_t
+puglDecodeUTF8(const uint8_t* buf)
+{
+#define FAIL_IF(cond) { if (cond) return 0xFFFD; }
+
+ // http://en.wikipedia.org/wiki/UTF-8
+
+ if (buf[0] < 0x80) {
+ return buf[0];
+ } else if (buf[0] < 0xC2) {
+ return 0xFFFD;
+ } else if (buf[0] < 0xE0) {
+ FAIL_IF((buf[1] & 0xC0) != 0x80);
+ return (buf[0] << 6) + buf[1] - 0x3080;
+ } else if (buf[0] < 0xF0) {
+ FAIL_IF((buf[1] & 0xC0) != 0x80);
+ FAIL_IF(buf[0] == 0xE0 && buf[1] < 0xA0);
+ FAIL_IF((buf[2] & 0xC0) != 0x80);
+ return (buf[0] << 12) + (buf[1] << 6) + buf[2] - 0xE2080;
+ } else if (buf[0] < 0xF5) {
+ FAIL_IF((buf[1] & 0xC0) != 0x80);
+ FAIL_IF(buf[0] == 0xF0 && buf[1] < 0x90);
+ FAIL_IF(buf[0] == 0xF4 && buf[1] >= 0x90);
+ FAIL_IF((buf[2] & 0xC0) != 0x80);
+ FAIL_IF((buf[3] & 0xC0) != 0x80);
+ return ((buf[0] << 18) +
+ (buf[1] << 12) +
+ (buf[2] << 6) +
+ buf[3] - 0x3C82080);
+ }
+ return 0xFFFD;
+}
+
+static void
+puglDispatchEvent(PuglView* view, const PuglEvent* event)
+{
+ switch (event->type) {
+ case PUGL_NOTHING:
+ break;
+ case PUGL_CONFIGURE:
+ view->width = event->configure.width;
+ view->height = event->configure.height;
+ puglEnterContext(view);
+ view->eventFunc(view, event);
+ puglLeaveContext(view, false);
+ break;
+ case PUGL_EXPOSE:
+ if (event->expose.count == 0) {
+ puglEnterContext(view);
+ view->eventFunc(view, event);
+ puglLeaveContext(view, true);
+ }
+ break;
+ default:
+ view->eventFunc(view, event);
+ }
+}
diff --git a/pugl/pugl/pugl_osx.m b/pugl/pugl/pugl_osx.m
new file mode 100644
index 0000000..d2681cb
--- /dev/null
+++ b/pugl/pugl/pugl_osx.m
@@ -0,0 +1,648 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_osx.m OSX/Cocoa Pugl Implementation.
+*/
+
+#include <stdlib.h>
+
+#import <Cocoa/Cocoa.h>
+
+#include "pugl/cairo_gl.h"
+#include "pugl/gl.h"
+#include "pugl/pugl_internal.h"
+
+@class PuglOpenGLView;
+
+struct PuglInternalsImpl {
+ NSApplication* app;
+ PuglOpenGLView* glview;
+ id window;
+ NSEvent* nextEvent;
+#ifdef PUGL_HAVE_CAIRO
+ cairo_surface_t* surface;
+ cairo_t* cr;
+ PuglCairoGL cairo_gl;
+#endif
+};
+
+@interface PuglWindow : NSWindow
+{
+@public
+ PuglView* puglview;
+}
+
+- (id) initWithContentRect:(NSRect)contentRect
+ styleMask:(unsigned int)aStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)flag;
+- (void) setPuglview:(PuglView*)view;
+- (BOOL) windowShouldClose:(id)sender;
+- (BOOL) canBecomeKeyWindow:(id)sender;
+@end
+
+@implementation PuglWindow
+
+- (id)initWithContentRect:(NSRect)contentRect
+ styleMask:(unsigned int)aStyle
+ backing:(NSBackingStoreType)bufferingType
+ defer:(BOOL)flag
+{
+ if (![super initWithContentRect:contentRect
+ styleMask:(NSClosableWindowMask |
+ NSTitledWindowMask |
+ NSResizableWindowMask)
+ backing:NSBackingStoreBuffered defer:NO]) {
+ return nil;
+ }
+
+ [self setAcceptsMouseMovedEvents:YES];
+ return (PuglWindow*)self;
+}
+
+- (void)setPuglview:(PuglView*)view
+{
+ puglview = view;
+ [self setContentSize:NSMakeSize(view->width, view->height)];
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ const PuglEventClose ev = {
+ PUGL_CLOSE,
+ puglview,
+ 0
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+
+ return YES;
+}
+
+- (BOOL) canBecomeKeyWindow
+{
+ return YES;
+}
+
+- (BOOL) canBecomeMainWindow
+{
+ return YES;
+}
+
+- (BOOL) canBecomeKeyWindow:(id)sender
+{
+ return NO;
+}
+
+@end
+
+@interface PuglOpenGLView : NSOpenGLView
+{
+@public
+ PuglView* puglview;
+
+ NSTrackingArea* trackingArea;
+}
+
+- (id) initWithFrame:(NSRect)frame;
+- (void) reshape;
+- (void) drawRect:(NSRect)rect;
+- (NSPoint) eventLocation:(NSEvent*)event;
+- (void) mouseEntered:(NSEvent*)event;
+- (void) mouseExited:(NSEvent*)event;
+- (void) mouseMoved:(NSEvent*)event;
+- (void) mouseDragged:(NSEvent*)event;
+- (void) rightMouseDragged:(NSEvent*)event;
+- (void) mouseDown:(NSEvent*)event;
+- (void) mouseUp:(NSEvent*)event;
+- (void) rightMouseDragged:(NSEvent*)event;
+- (void) rightMouseDown:(NSEvent*)event;
+- (void) rightMouseUp:(NSEvent*)event;
+- (void) otherMouseDragged:(NSEvent*)event;
+- (void) otherMouseDown:(NSEvent*)event;
+- (void) otherMouseUp:(NSEvent*)event;
+- (void) scrollWheel:(NSEvent*)event;
+- (void) keyDown:(NSEvent*)event;
+- (void) keyUp:(NSEvent*)event;
+- (void) flagsChanged:(NSEvent*)event;
+
+@end
+
+@implementation PuglOpenGLView
+
+- (id) initWithFrame:(NSRect)frame
+{
+ NSOpenGLPixelFormatAttribute pixelAttribs[16] = {
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAccelerated,
+ NSOpenGLPFAColorSize, 32,
+ NSOpenGLPFADepthSize, 32,
+ 0
+ };
+
+ NSOpenGLPixelFormat* pixelFormat = [
+ [NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs];
+
+ if (pixelFormat) {
+ self = [super initWithFrame:frame pixelFormat:pixelFormat];
+ [pixelFormat release];
+ } else {
+ self = [super initWithFrame:frame];
+ }
+
+ if (self) {
+ [[self openGLContext] makeCurrentContext];
+ [self reshape];
+ }
+ return self;
+}
+
+- (void) reshape
+{
+ [[self openGLContext] update];
+
+ if (!puglview) {
+ return;
+ }
+
+ const NSRect bounds = [self bounds];
+ const PuglEventConfigure ev = {
+ PUGL_CONFIGURE,
+ puglview,
+ 0,
+ bounds.origin.x,
+ bounds.origin.y,
+ bounds.size.width,
+ bounds.size.height,
+ };
+
+#ifdef PUGL_HAVE_CAIRO
+ PuglInternals* impl = puglview->impl;
+ if (puglview->ctx_type & PUGL_CAIRO) {
+ cairo_surface_destroy(impl->surface);
+ cairo_destroy(impl->cr);
+ impl->surface = pugl_cairo_gl_create(
+ &impl->cairo_gl, ev.width, ev.height, 4);
+ impl->cr = cairo_create(impl->surface);
+ pugl_cairo_gl_configure(&impl->cairo_gl, ev.width, ev.height);
+ }
+#endif
+
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) drawRect:(NSRect)rect
+{
+ const PuglEventExpose ev = {
+ PUGL_EXPOSE,
+ puglview,
+ 0,
+ rect.origin.x,
+ rect.origin.y,
+ rect.size.width,
+ rect.size.height,
+ 0
+ };
+
+ puglDispatchEvent(puglview, (const PuglEvent*)&ev);
+
+#ifdef PUGL_HAVE_CAIRO
+ if (puglview->ctx_type & PUGL_CAIRO) {
+ pugl_cairo_gl_draw(
+ &puglview->impl->cairo_gl, puglview->width, puglview->height);
+ }
+#endif
+}
+
+- (BOOL) acceptsFirstResponder
+{
+ return YES;
+}
+
+static unsigned
+getModifiers(PuglView* view, NSEvent* ev)
+{
+ const unsigned modifierFlags = [ev modifierFlags];
+
+ unsigned mods = 0;
+ mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0;
+ mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0;
+ mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0;
+ mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0;
+ return mods;
+}
+
+-(void)updateTrackingAreas
+{
+ if (trackingArea != nil) {
+ [self removeTrackingArea:trackingArea];
+ [trackingArea release];
+ }
+
+ const int opts = (NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingActiveAlways);
+ trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds]
+ options:opts
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:trackingArea];
+}
+
+- (NSPoint) eventLocation:(NSEvent*)event
+{
+ return [self convertPoint:[event locationInWindow] fromView:nil];
+}
+
+- (void)mouseEntered:(NSEvent*)theEvent
+{
+ [self updateTrackingAreas];
+}
+
+- (void)mouseExited:(NSEvent*)theEvent
+{
+}
+
+- (void) mouseMoved:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventMotion ev = {
+ PUGL_MOTION_NOTIFY,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ 0,
+ 1
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) mouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+}
+
+- (void) rightMouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+}
+
+- (void) otherMouseDragged:(NSEvent*)event
+{
+ [self mouseMoved: event];
+}
+
+- (void) mouseDown:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventButton ev = {
+ PUGL_BUTTON_PRESS,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event buttonNumber] + 1
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) mouseUp:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventButton ev = {
+ PUGL_BUTTON_RELEASE,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event buttonNumber] + 1
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+ [self updateTrackingAreas];
+}
+
+- (void) rightMouseDown:(NSEvent*)event
+{
+ [self mouseDown: event];
+}
+
+- (void) rightMouseUp:(NSEvent*)event
+{
+ [self mouseUp: event];
+}
+
+- (void) otherMouseDown:(NSEvent*)event
+{
+ [self mouseDown: event];
+}
+
+- (void) otherMouseUp:(NSEvent*)event
+{
+ [self mouseUp: event];
+}
+
+- (void) scrollWheel:(NSEvent*)event
+{
+ [self updateTrackingAreas];
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const PuglEventScroll ev = {
+ PUGL_SCROLL,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event deltaX],
+ [event deltaY]
+ };
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+ [self updateTrackingAreas];
+}
+
+- (void) keyDown:(NSEvent*)event
+{
+ if (puglview->ignoreKeyRepeat && [event isARepeat]) {
+ return;
+ }
+
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const NSString* chars = [event characters];
+ const char* str = [chars UTF8String];
+ PuglEventKey ev = {
+ PUGL_KEY_PRESS,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event keyCode],
+ puglDecodeUTF8((const uint8_t*)str),
+ 0, // TODO: Special keys?
+ { 0, 0, 0, 0, 0, 0, 0, 0 },
+ false
+ };
+ strncpy((char*)ev.utf8, str, 8);
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) keyUp:(NSEvent*)event
+{
+ const NSPoint wloc = [self eventLocation:event];
+ const NSPoint rloc = [NSEvent mouseLocation];
+ const NSString* chars = [event characters];
+ const char* str = [chars UTF8String];
+ const PuglEventKey ev = {
+ PUGL_KEY_RELEASE,
+ puglview,
+ 0,
+ [event timestamp],
+ wloc.x,
+ puglview->height - wloc.y,
+ rloc.x,
+ [[NSScreen mainScreen] frame].size.height - rloc.y,
+ getModifiers(puglview, event),
+ [event keyCode],
+ puglDecodeUTF8((const uint8_t*)str),
+ 0, // TODO: Special keys?
+ { 0, 0, 0, 0, 0, 0, 0, 0 },
+ false,
+ };
+ strncpy((char*)ev.utf8, str, 8);
+ puglDispatchEvent(puglview, (PuglEvent*)&ev);
+}
+
+- (void) flagsChanged:(NSEvent*)event
+{
+ // TODO: Is this a sensible way to handle special keys?
+ /*
+ const unsigned mods = getModifiers(puglview, event);
+ if ((mods & PUGL_MOD_SHIFT) != (puglview->mods & PUGL_MOD_SHIFT)) {
+ puglview->specialFunc(puglview, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT);
+ } else if ((mods & PUGL_MOD_CTRL) != (puglview->mods & PUGL_MOD_CTRL)) {
+ puglview->specialFunc(puglview, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL);
+ } else if ((mods & PUGL_MOD_ALT) != (puglview->mods & PUGL_MOD_ALT)) {
+ puglview->specialFunc(puglview, mods & PUGL_MOD_ALT, PUGL_KEY_ALT);
+ } else if ((mods & PUGL_MOD_SUPER) != (puglview->mods & PUGL_MOD_SUPER)) {
+ puglview->specialFunc(puglview, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER);
+ }
+ puglview->mods = mods;
+ }
+ */
+}
+
+@end
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+void
+puglEnterContext(PuglView* view)
+{
+ [[view->impl->glview openGLContext] makeCurrentContext];
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ cairo_set_source_rgb(view->impl->cr, 0, 0, 0);
+ cairo_paint(view->impl->cr);
+ }
+#endif
+}
+
+void
+puglLeaveContext(PuglView* view, bool flush)
+{
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
+ }
+#endif
+
+ if (flush) {
+ [[view->impl->glview openGLContext] flushBuffer];
+ }
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* impl = view->impl;
+
+ [NSAutoreleasePool new];
+ impl->app = [NSApplication sharedApplication];
+
+ impl->glview = [PuglOpenGLView new];
+ impl->glview->puglview = view;
+
+ if (view->transient_parent) {
+ NSView* pview = (NSView*)view->transient_parent;
+ [pview addSubview:impl->glview];
+ [impl->glview setHidden:NO];
+ } else {
+ NSString* titleString = [[NSString alloc]
+ initWithBytes:title
+ length:strlen(title)
+ encoding:NSUTF8StringEncoding];
+
+ id window = [[PuglWindow new] retain];
+ [window setPuglview:view];
+ [window setTitle:titleString];
+ if (view->min_width || view->min_height) {
+ [window setContentMinSize:NSMakeSize(view->min_width,
+ view->min_height)];
+ }
+ impl->window = window;
+
+ [window setContentView:impl->glview];
+ [impl->app activateIgnoringOtherApps:YES];
+ [window makeFirstResponder:impl->glview];
+ [window makeKeyAndOrderFront:window];
+#if 0
+ if (resizable) {
+ [impl->glview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
+ }
+#endif
+ }
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:YES];
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ [view->impl->window setIsVisible:NO];
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+#ifdef PUGL_HAVE_CAIRO
+ pugl_cairo_gl_free(&view->impl->cairo_gl);
+#endif
+ view->impl->glview->puglview = NULL;
+ [view->impl->glview removeFromSuperview];
+ if (view->impl->window) {
+ [view->impl->window close];
+ }
+ [view->impl->glview release];
+ if (view->impl->window) {
+ [view->impl->window release];
+ }
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ // TODO
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ /* OSX supposedly has queue: and untilDate: selectors that can be used for
+ a blocking non-queueing event check, but if used here cause an
+ unsupported selector error at runtime. I have no idea why, so just get
+ the event and keep it around until the call to puglProcessEvents. */
+ if (!view->impl->nextEvent) {
+ view->impl->nextEvent = [view->impl->window
+ nextEventMatchingMask: NSAnyEventMask];
+ }
+
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ while (true) {
+ // Get the next event, or use the cached one from puglWaitForEvent
+ if (!view->impl->nextEvent) {
+ view->impl->nextEvent = [view->impl->window
+ nextEventMatchingMask: NSAnyEventMask];
+ }
+
+ if (!view->impl->nextEvent) {
+ break; // No events to process, done
+ }
+
+ // Dispatch event
+ [view->impl->app sendEvent: view->impl->nextEvent];
+ view->impl->nextEvent = NULL;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ //view->redisplay = true; // unused
+ [view->impl->glview setNeedsDisplay: YES];
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->glview;
+}
+
+void*
+puglGetContext(PuglView* view)
+{
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ return view->impl->cr;
+ }
+#endif
+ return NULL;
+}
diff --git a/pugl/pugl/pugl_win.cpp b/pugl/pugl/pugl_win.cpp
new file mode 100644
index 0000000..83d4474
--- /dev/null
+++ b/pugl/pugl/pugl_win.cpp
@@ -0,0 +1,644 @@
+/*
+ Copyright 2012-2015 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_win.cpp Windows/WGL Pugl Implementation.
+*/
+
+#include <windows.h>
+#include <windowsx.h>
+#include <GL/gl.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wctype.h>
+
+#include "pugl/pugl_internal.h"
+
+#ifndef WM_MOUSEWHEEL
+# define WM_MOUSEWHEEL 0x020A
+#endif
+#ifndef WM_MOUSEHWHEEL
+# define WM_MOUSEHWHEEL 0x020E
+#endif
+#ifndef WHEEL_DELTA
+# define WHEEL_DELTA 120
+#endif
+#ifdef _WIN64
+# ifndef GWLP_USERDATA
+# define GWLP_USERDATA (-21)
+# endif
+#else
+# ifndef GWL_USERDATA
+# define GWL_USERDATA (-21)
+# endif
+#endif
+
+#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
+
+struct PuglInternalsImpl {
+ HWND hwnd;
+ HDC hdc;
+ HGLRC hglrc;
+ WNDCLASS wc;
+};
+
+LRESULT CALLBACK
+wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
+
+PuglView*
+puglInit()
+{
+ PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
+ PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
+ if (!view || !impl) {
+ return NULL;
+ }
+
+ view->impl = impl;
+ view->width = 640;
+ view->height = 480;
+
+ return view;
+}
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+void
+puglEnterContext(PuglView* view)
+{
+ PAINTSTRUCT ps;
+ BeginPaint(view->impl->hwnd, &ps);
+
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type == PUGL_GL) {
+ wglMakeCurrent(view->impl->hdc, view->impl->hglrc);
+ }
+#endif
+}
+
+void
+puglLeaveContext(PuglView* view, bool flush)
+{
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type == PUGL_GL && flush) {
+ glFlush();
+ SwapBuffers(view->impl->hdc);
+ }
+#endif
+
+ PAINTSTRUCT ps;
+ EndPaint(view->impl->hwnd, &ps);
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ static const TCHAR* DEFAULT_CLASSNAME = "Pugl";
+
+ PuglInternals* impl = view->impl;
+
+ if (!title) {
+ title = "Window";
+ }
+
+ WNDCLASSEX wc;
+ memset(&wc, 0, sizeof(wc));
+ wc.cbSize = sizeof(wc);
+ wc.style = CS_OWNDC;
+ wc.lpfnWndProc = wndProc;
+ wc.hInstance = GetModuleHandle(NULL);
+ wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // TODO: user-specified icon
+ wc.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
+ wc.lpszClassName = view->windowClass ? view->windowClass : DEFAULT_CLASSNAME;
+ if (!RegisterClassEx(&wc)) {
+ free((void*)impl->wc.lpszClassName);
+ free(impl);
+ free(view);
+ return NULL;
+ }
+
+ int winFlags = WS_POPUPWINDOW | WS_CAPTION;
+ if (view->resizable) {
+ winFlags |= WS_SIZEBOX;
+ if (view->min_width || view->min_height) {
+ // Adjust the minimum window size to accomodate requested view size
+ RECT mr = { 0, 0, view->min_width, view->min_height };
+ AdjustWindowRectEx(&mr, winFlags, FALSE, WS_EX_TOPMOST);
+ view->min_width = mr.right - mr.left;
+ view->min_height = mr.bottom - mr.top;
+ }
+ }
+
+ // Adjust the window size to accomodate requested view size
+ RECT wr = { 0, 0, view->width, view->height };
+ AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST);
+
+ impl->hwnd = CreateWindowEx(
+ WS_EX_TOPMOST,
+ wc.lpszClassName, title,
+ (view->parent ? WS_CHILD : winFlags),
+ CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top,
+ (HWND)view->parent, NULL, NULL, NULL);
+
+ if (!impl->hwnd) {
+ free((void*)impl->wc.lpszClassName);
+ free(impl);
+ free(view);
+ return 1;
+ }
+
+#ifdef _WIN64
+ SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
+#else
+ SetWindowLongPtr(impl->hwnd, GWL_USERDATA, (LONG)view);
+#endif
+
+ impl->hdc = GetDC(impl->hwnd);
+
+ PIXELFORMATDESCRIPTOR pfd;
+ ZeroMemory(&pfd, sizeof(pfd));
+ pfd.nSize = sizeof(pfd);
+ pfd.nVersion = 1;
+ pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+ pfd.iPixelType = PFD_TYPE_RGBA;
+ pfd.cColorBits = 24;
+ pfd.cDepthBits = 16;
+ pfd.iLayerType = PFD_MAIN_PLANE;
+
+ int format = ChoosePixelFormat(impl->hdc, &pfd);
+ SetPixelFormat(impl->hdc, format, &pfd);
+
+ impl->hglrc = wglCreateContext(impl->hdc);
+ if (!impl->hglrc) {
+ ReleaseDC(impl->hwnd, impl->hdc);
+ DestroyWindow(impl->hwnd);
+ UnregisterClass(impl->wc.lpszClassName, NULL);
+ free((void*)impl->wc.lpszClassName);
+ free(impl);
+ free(view);
+ return NULL;
+ }
+ wglMakeCurrent(impl->hdc, impl->hglrc);
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ ShowWindow(impl->hwnd, SW_SHOWNORMAL);
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ PuglInternals* impl = view->impl;
+
+ ShowWindow(impl->hwnd, SW_HIDE);
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ wglMakeCurrent(NULL, NULL);
+ wglDeleteContext(view->impl->hglrc);
+ ReleaseDC(view->impl->hwnd, view->impl->hdc);
+ DestroyWindow(view->impl->hwnd);
+ UnregisterClass(view->impl->wc.lpszClassName, NULL);
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+}
+
+static PuglKey
+keySymToSpecial(int sym)
+{
+ switch (sym) {
+ case VK_F1: return PUGL_KEY_F1;
+ case VK_F2: return PUGL_KEY_F2;
+ case VK_F3: return PUGL_KEY_F3;
+ case VK_F4: return PUGL_KEY_F4;
+ case VK_F5: return PUGL_KEY_F5;
+ case VK_F6: return PUGL_KEY_F6;
+ case VK_F7: return PUGL_KEY_F7;
+ case VK_F8: return PUGL_KEY_F8;
+ case VK_F9: return PUGL_KEY_F9;
+ case VK_F10: return PUGL_KEY_F10;
+ case VK_F11: return PUGL_KEY_F11;
+ case VK_F12: return PUGL_KEY_F12;
+ case VK_LEFT: return PUGL_KEY_LEFT;
+ case VK_UP: return PUGL_KEY_UP;
+ case VK_RIGHT: return PUGL_KEY_RIGHT;
+ case VK_DOWN: return PUGL_KEY_DOWN;
+ case VK_PRIOR: return PUGL_KEY_PAGE_UP;
+ case VK_NEXT: return PUGL_KEY_PAGE_DOWN;
+ case VK_HOME: return PUGL_KEY_HOME;
+ case VK_END: return PUGL_KEY_END;
+ case VK_INSERT: return PUGL_KEY_INSERT;
+ case VK_SHIFT: return PUGL_KEY_SHIFT;
+ case VK_CONTROL: return PUGL_KEY_CTRL;
+ case VK_MENU: return PUGL_KEY_ALT;
+ case VK_LWIN: return PUGL_KEY_SUPER;
+ case VK_RWIN: return PUGL_KEY_SUPER;
+ }
+ return (PuglKey)0;
+}
+
+static unsigned int
+getModifiers()
+{
+ unsigned int mods = 0;
+ mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0;
+ mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0;
+ mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0;
+ mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0;
+ mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0;
+ return mods;
+}
+
+static void
+initMouseEvent(PuglEvent* event,
+ PuglView* view,
+ int button,
+ bool press,
+ LPARAM lParam)
+{
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ ClientToScreen(view->impl->hwnd, &pt);
+
+ if (press) {
+ SetCapture(view->impl->hwnd);
+ } else {
+ ReleaseCapture();
+ }
+
+ event->button.time = GetMessageTime();
+ event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
+ event->button.x = GET_X_LPARAM(lParam);
+ event->button.y = GET_Y_LPARAM(lParam);
+ event->button.x_root = pt.x;
+ event->button.y_root = pt.y;
+ event->button.state = getModifiers();
+ event->button.button = button;
+}
+
+static void
+initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam, WPARAM wParam)
+{
+ POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ ScreenToClient(view->impl->hwnd, &pt);
+
+ event->scroll.time = GetMessageTime();
+ event->scroll.type = PUGL_SCROLL;
+ event->scroll.x = pt.x;
+ event->scroll.y = pt.y;
+ event->scroll.x_root = GET_X_LPARAM(lParam);
+ event->scroll.y_root = GET_Y_LPARAM(lParam);
+ event->scroll.state = getModifiers();
+ event->scroll.dx = 0;
+ event->scroll.dy = 0;
+}
+
+static unsigned int
+utf16_to_code_point(const wchar_t* input, size_t input_size)
+{
+ unsigned int code_unit = *input;
+ // Equiv. range check between 0xD800 to 0xDBFF inclusive
+ if ((code_unit & 0xFC00) == 0xD800) {
+ if (input_size < 2) {
+ // "Error: is surrogate but input_size too small"
+ return 0xFFFD; // replacement character
+ }
+
+ unsigned int code_unit_2 = *++input;
+ // Equiv. range check between 0xDC00 to 0xDFFF inclusive
+ if ((code_unit_2 & 0xFC00) == 0xDC00) {
+ return (code_unit << 10) + code_unit_2 - 0x35FDC00;
+ }
+
+ // TODO: push_back(code_unit_2);
+ // "Error: Unpaired surrogates."
+ return 0xFFFD; // replacement character
+ }
+ return code_unit;
+}
+
+static void
+initKeyEvent(PuglEvent* event, PuglView* view, bool press, LPARAM lParam)
+{
+ POINT rpos = { 0, 0 };
+ GetCursorPos(&rpos);
+
+ POINT cpos = { rpos.x, rpos.y };
+ ScreenToClient(view->impl->hwnd, &rpos);
+
+ event->key.type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
+ event->key.time = GetMessageTime();
+ event->key.state = getModifiers();
+ event->key.x_root = rpos.x;
+ event->key.y_root = rpos.y;
+ event->key.x = cpos.x;
+ event->key.y = cpos.y;
+ event->key.keycode = (lParam & 0xFF0000) >> 16;
+ event->key.character = 0;
+ event->key.special = static_cast<PuglKey>(0);
+ event->key.filter = 0;
+}
+
+static void
+wcharBufToEvent(wchar_t* buf, int n, PuglEvent* event)
+{
+ if (n > 0) {
+ char* charp = reinterpret_cast<char*>(event->key.utf8);
+ if (!WideCharToMultiByte(CP_UTF8, 0, buf, n,
+ charp, 8, NULL, NULL)) {
+ /* error: could not convert to utf-8,
+ GetLastError has details */
+ memset(event->key.utf8, 0, 8);
+ // replacement character
+ event->key.utf8[0] = 0xEF;
+ event->key.utf8[1] = 0xBF;
+ event->key.utf8[2] = 0xBD;
+ }
+
+ event->key.character = utf16_to_code_point(buf, n);
+ } else {
+ // replacement character
+ event->key.utf8[0] = 0xEF;
+ event->key.utf8[1] = 0xBF;
+ event->key.utf8[2] = 0xBD;
+ event->key.character = 0xFFFD;
+ }
+}
+
+static void
+translateMessageParamsToEvent(LPARAM lParam, WPARAM wParam, PuglEvent* event)
+{
+ /* TODO: This is a kludge. Would be nice to use ToUnicode here, but this
+ breaks composed keys because it messes with the keyboard state. Not
+ sure how to correctly handle this on Windows. */
+
+ // This is how I really want to do this, but it breaks composed keys (é,
+ // è, ü, ö, and so on) because ToUnicode messes with the keyboard state.
+
+ //wchar_t buf[5];
+ //BYTE keyboard_state[256];
+ //int wcharCount = 0;
+ //GetKeyboardState(keyboard_state);
+ //wcharCount = ToUnicode(wParam, MapVirtualKey(wParam, MAPVK_VK_TO_VSC),
+ // keyboard_state, buf, 4, 0);
+ //wcharBufToEvent(buf, wcharCount, event);
+
+ // So, since Google refuses to give me a better solution, and if no one
+ // else has a better solution, I will make a hack...
+ wchar_t buf[5] = { 0, 0, 0, 0, 0 };
+ UINT c = MapVirtualKey(wParam, MAPVK_VK_TO_CHAR);
+ buf[0] = c & 0xffff;
+ // TODO: This does not take caps lock into account
+ // TODO: Dead keys should affect key releases as well
+ if (!(event->key.state && PUGL_MOD_SHIFT))
+ buf[0] = towlower(buf[0]);
+ wcharBufToEvent(buf, 1, event);
+ event->key.filter = ((c >> 31) & 0x1);
+}
+
+static void
+translateCharEventToEvent(WPARAM wParam, PuglEvent* event)
+{
+ wchar_t buf[2];
+ int wcharCount;
+ if (wParam & 0xFFFF0000) {
+ wcharCount = 2;
+ buf[0] = (wParam & 0xFFFF);
+ buf[1] = ((wParam >> 16) & 0xFFFF);
+ } else {
+ wcharCount = 1;
+ buf[0] = (wParam & 0xFFFF);
+ }
+ wcharBufToEvent(buf, wcharCount, event);
+}
+
+static bool
+ignoreKeyEvent(PuglView* view, LPARAM lParam)
+{
+ return view->ignoreKeyRepeat && (lParam & (1 << 30));
+}
+
+static LRESULT
+handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ PuglEvent event;
+ void* dummy_ptr = NULL;
+ RECT rect;
+ MINMAXINFO* mmi;
+ POINT pt;
+ bool dispatchThisEvent = true;
+
+ memset(&event, 0, sizeof(event));
+
+ event.any.type = PUGL_NOTHING;
+ event.any.view = view;
+ if (InSendMessageEx(dummy_ptr)) {
+ event.any.flags |= PUGL_IS_SEND_EVENT;
+ }
+
+ switch (message) {
+ case WM_CREATE:
+ case WM_SHOWWINDOW:
+ case WM_SIZE:
+ GetWindowRect(view->impl->hwnd, &rect);
+ event.configure.type = PUGL_CONFIGURE;
+ event.configure.x = rect.left;
+ event.configure.y = rect.top;
+ view->width = rect.right - rect.left;
+ view->height = rect.bottom - rect.top;
+ event.configure.width = view->width;
+ event.configure.height = view->height;
+ break;
+ case WM_GETMINMAXINFO:
+ mmi = (MINMAXINFO*)lParam;
+ mmi->ptMinTrackSize.x = view->min_width;
+ mmi->ptMinTrackSize.y = view->min_height;
+ break;
+ case WM_PAINT:
+ GetUpdateRect(view->impl->hwnd, &rect, false);
+ event.expose.type = PUGL_EXPOSE;
+ event.expose.x = rect.left;
+ event.expose.y = rect.top;
+ event.expose.width = rect.right - rect.left;
+ event.expose.height = rect.bottom - rect.top;
+ event.expose.count = 0;
+ break;
+ case WM_MOUSEMOVE:
+ pt.x = GET_X_LPARAM(lParam);
+ pt.y = GET_Y_LPARAM(lParam);
+ ClientToScreen(view->impl->hwnd, &pt);
+
+ event.motion.type = PUGL_MOTION_NOTIFY;
+ event.motion.time = GetMessageTime();
+ event.motion.x = GET_X_LPARAM(lParam);
+ event.motion.y = GET_Y_LPARAM(lParam);
+ event.motion.x_root = pt.x;
+ event.motion.y_root = pt.y;
+ event.motion.state = getModifiers();
+ event.motion.is_hint = false;
+ break;
+ case WM_LBUTTONDOWN:
+ initMouseEvent(&event, view, 1, true, lParam);
+ break;
+ case WM_MBUTTONDOWN:
+ initMouseEvent(&event, view, 2, true, lParam);
+ break;
+ case WM_RBUTTONDOWN:
+ initMouseEvent(&event, view, 3, true, lParam);
+ break;
+ case WM_LBUTTONUP:
+ initMouseEvent(&event, view, 1, false, lParam);
+ break;
+ case WM_MBUTTONUP:
+ initMouseEvent(&event, view, 2, false, lParam);
+ break;
+ case WM_RBUTTONUP:
+ initMouseEvent(&event, view, 3, false, lParam);
+ break;
+ case WM_MOUSEWHEEL:
+ initScrollEvent(&event, view, lParam, wParam);
+ event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ break;
+ case WM_MOUSEHWHEEL:
+ initScrollEvent(&event, view, lParam, wParam);
+ event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA;
+ break;
+ case WM_KEYDOWN:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event, view, true, lParam);
+ if (!(event.key.special = keySymToSpecial(wParam))) {
+ event.key.type = PUGL_NOTHING;
+ }
+ }
+ break;
+ case WM_CHAR:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event, view, true, lParam);
+ translateCharEventToEvent(wParam, &event);
+ }
+ break;
+ case WM_DEADCHAR:
+ if (!ignoreKeyEvent(view, lParam)) {
+ initKeyEvent(&event, view, true, lParam);
+ translateCharEventToEvent(wParam, &event);
+ event.key.filter = 1;
+ }
+ break;
+ case WM_KEYUP:
+ initKeyEvent(&event, view, false, lParam);
+ if (!(event.key.special = keySymToSpecial(wParam))) {
+ translateMessageParamsToEvent(lParam, wParam, &event);
+ }
+ break;
+ case WM_QUIT:
+ case PUGL_LOCAL_CLOSE_MSG:
+ event.close.type = PUGL_CLOSE;
+ break;
+ default:
+ return DefWindowProc(
+ view->impl->hwnd, message, wParam, lParam);
+ }
+
+ puglDispatchEvent(view, &event);
+
+ return 0;
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ // TODO
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ WaitMessage();
+ return PUGL_SUCCESS;
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ MSG msg;
+ while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
+ TranslateMessage(&msg);
+ handleMessage(view, msg.message, msg.wParam, msg.lParam);
+ }
+
+ if (view->redisplay) {
+ InvalidateRect(view->impl->hwnd, NULL, FALSE);
+ view->redisplay = false;
+ }
+
+ return PUGL_SUCCESS;
+}
+
+LRESULT CALLBACK
+wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+#ifdef _WIN64
+ PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+#else
+ PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWL_USERDATA);
+#endif
+
+ switch (message) {
+ case WM_CREATE:
+ PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
+ return 0;
+ case WM_CLOSE:
+ PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam);
+ return 0;
+ case WM_DESTROY:
+ return 0;
+ default:
+ if (view && hwnd == view->impl->hwnd) {
+ return handleMessage(view, message, wParam, lParam);
+ } else {
+ return DefWindowProc(hwnd, message, wParam, lParam);
+ }
+ }
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ view->redisplay = true;
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return (PuglNativeWindow)view->impl->hwnd;
+}
diff --git a/pugl/pugl/pugl_x11.c b/pugl/pugl/pugl_x11.c
new file mode 100644
index 0000000..6a9135b
--- /dev/null
+++ b/pugl/pugl/pugl_x11.c
@@ -0,0 +1,721 @@
+/*
+ Copyright 2012-2016 David Robillard <http://drobilla.net>
+ Copyright 2013 Robin Gareus <robin@gareus.org>
+ Copyright 2011-2012 Ben Loftis, Harrison Consoles
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_x11.c X11 Pugl Implementation.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <X11/Xatom.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#ifdef PUGL_HAVE_GL
+#include <GL/gl.h>
+#include <GL/glx.h>
+#endif
+
+#ifdef PUGL_HAVE_CAIRO
+#include <cairo/cairo.h>
+#include <cairo/cairo-xlib.h>
+#endif
+
+#include "pugl/cairo_gl.h"
+#include "pugl/pugl_internal.h"
+
+#ifndef MIN
+# define MIN(a, b) (((a) < (b)) ? (a) : (b))
+#endif
+
+#ifndef MAX
+# define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
+
+#ifdef PUGL_HAVE_GL
+
+/** Attributes for double-buffered RGBA. */
+static int attrListDbl[] = {
+ GLX_RGBA,
+ GLX_DOUBLEBUFFER , True,
+ GLX_RED_SIZE , 4,
+ GLX_GREEN_SIZE , 4,
+ GLX_BLUE_SIZE , 4,
+ GLX_DEPTH_SIZE , 16,
+ /* GLX_SAMPLE_BUFFERS , 1, */
+ /* GLX_SAMPLES , 4, */
+ None
+};
+
+/** Attributes for single-buffered RGBA. */
+static int attrListSgl[] = {
+ GLX_RGBA,
+ GLX_DOUBLEBUFFER , False,
+ GLX_RED_SIZE , 4,
+ GLX_GREEN_SIZE , 4,
+ GLX_BLUE_SIZE , 4,
+ GLX_DEPTH_SIZE , 16,
+ /* GLX_SAMPLE_BUFFERS , 1, */
+ /* GLX_SAMPLES , 4, */
+ None
+};
+
+/** Null-terminated list of attributes in order of preference. */
+static int* attrLists[] = { attrListDbl, attrListSgl, NULL };
+
+#endif // PUGL_HAVE_GL
+
+struct PuglInternalsImpl {
+ Display* display;
+ int screen;
+ Window win;
+ XIM xim;
+ XIC xic;
+#ifdef PUGL_HAVE_CAIRO
+ cairo_surface_t* surface;
+ cairo_t* cr;
+#endif
+#ifdef PUGL_HAVE_GL
+ GLXContext ctx;
+ int doubleBuffered;
+#endif
+#if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
+ PuglCairoGL cairo_gl;
+#endif
+};
+
+PuglInternals*
+puglInitInternals(void)
+{
+ return (PuglInternals*)calloc(1, sizeof(PuglInternals));
+}
+
+static XVisualInfo*
+getVisual(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+ XVisualInfo* vi = NULL;
+
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ for (int* attr = *attrLists; !vi && *attr; ++attr) {
+ vi = glXChooseVisual(impl->display, impl->screen, attr);
+ }
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO) {
+ XVisualInfo pat;
+ int n;
+ pat.screen = impl->screen;
+ vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
+ }
+#endif
+
+ return vi;
+}
+
+#ifdef PUGL_HAVE_CAIRO
+static int
+createCairoContext(PuglView* view)
+{
+ PuglInternals* const impl = view->impl;
+
+ if (impl->cr) {
+ cairo_destroy(impl->cr);
+ }
+
+ impl->cr = cairo_create(impl->surface);
+ return cairo_status(impl->cr);
+}
+#endif
+
+static bool
+createContext(PuglView* view, XVisualInfo* vi)
+{
+ PuglInternals* const impl = view->impl;
+
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE);
+ glXGetConfig(impl->display, vi, GLX_DOUBLEBUFFER, &impl->doubleBuffered);
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO) {
+ impl->surface = cairo_xlib_surface_create(
+ impl->display, impl->win, vi->visual, view->width, view->height);
+ }
+#endif
+#if defined(PUGL_HAVE_GL) && defined(PUGL_HAVE_CAIRO)
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ impl->surface = pugl_cairo_gl_create(
+ &impl->cairo_gl, view->width, view->height, 4);
+ }
+#endif
+
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ if (cairo_surface_status(impl->surface) != CAIRO_STATUS_SUCCESS) {
+ fprintf(stderr, "error: failed to create cairo surface\n");
+ return false;
+ }
+
+ if (createCairoContext(view) != CAIRO_STATUS_SUCCESS) {
+ cairo_surface_destroy(impl->surface);
+ fprintf(stderr, "error: failed to create cairo context\n");
+ return false;
+ }
+ }
+#endif
+
+ return true;
+}
+
+static void
+destroyContext(PuglView* view)
+{
+#if defined(PUGL_HAVE_CAIRO) && defined(PUGL_HAVE_GL)
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ pugl_cairo_gl_free(&view->impl->cairo_gl);
+ }
+#endif
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ glXDestroyContext(view->impl->display, view->impl->ctx);
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ cairo_destroy(view->impl->cr);
+ cairo_surface_destroy(view->impl->surface);
+ }
+#endif
+}
+
+void
+puglEnterContext(PuglView* view)
+{
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type & PUGL_GL) {
+ glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
+ }
+#endif
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ cairo_set_source_rgb(view->impl->cr, 0, 0, 0);
+ cairo_paint(view->impl->cr);
+ }
+#endif
+}
+
+void
+puglLeaveContext(PuglView* view, bool flush)
+{
+#ifdef PUGL_HAVE_GL
+ if (flush && view->ctx_type & PUGL_GL) {
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ pugl_cairo_gl_draw(&view->impl->cairo_gl, view->width, view->height);
+ }
+#endif
+
+ glFlush();
+ if (view->impl->doubleBuffered) {
+ glXSwapBuffers(view->impl->display, view->impl->win);
+ }
+ }
+
+ glXMakeCurrent(view->impl->display, None, NULL);
+#endif
+}
+
+int
+puglCreateWindow(PuglView* view, const char* title)
+{
+ PuglInternals* const impl = view->impl;
+
+ impl->display = XOpenDisplay(0);
+ impl->screen = DefaultScreen(impl->display);
+
+ XVisualInfo* const vi = getVisual(view);
+ if (!vi) {
+ return 1;
+ }
+
+ Window xParent = view->parent
+ ? (Window)view->parent
+ : RootWindow(impl->display, impl->screen);
+
+ Colormap cmap = XCreateColormap(
+ impl->display, xParent, vi->visual, AllocNone);
+
+ XSetWindowAttributes attr;
+ memset(&attr, 0, sizeof(XSetWindowAttributes));
+ attr.colormap = cmap;
+ attr.event_mask = (ExposureMask | StructureNotifyMask |
+ EnterWindowMask | LeaveWindowMask |
+ KeyPressMask | KeyReleaseMask |
+ ButtonPressMask | ButtonReleaseMask |
+ PointerMotionMask | FocusChangeMask);
+
+ impl->win = XCreateWindow(
+ impl->display, xParent,
+ 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
+ CWColormap | CWEventMask, &attr);
+
+ if (!createContext(view, vi)) {
+ return 2;
+ }
+
+ XSizeHints sizeHints;
+ memset(&sizeHints, 0, sizeof(sizeHints));
+ if (!view->resizable) {
+ sizeHints.flags = PMinSize|PMaxSize;
+ sizeHints.min_width = view->width;
+ sizeHints.min_height = view->height;
+ sizeHints.max_width = view->width;
+ sizeHints.max_height = view->height;
+ XSetNormalHints(impl->display, impl->win, &sizeHints);
+ } else {
+ if (view->min_width || view->min_height) {
+ sizeHints.flags = PMinSize;
+ sizeHints.min_width = view->min_width;
+ sizeHints.min_height = view->min_height;
+ }
+ if (view->min_aspect_x) {
+ sizeHints.flags |= PAspect;
+ sizeHints.min_aspect.x = view->min_aspect_x;
+ sizeHints.min_aspect.y = view->min_aspect_y;
+ sizeHints.max_aspect.x = view->max_aspect_x;
+ sizeHints.max_aspect.y = view->max_aspect_y;
+ }
+
+ XSetNormalHints(impl->display, impl->win, &sizeHints);
+ }
+
+ if (title) {
+ XStoreName(impl->display, impl->win, title);
+ }
+
+ if (!view->parent) {
+ Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
+ XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
+ }
+
+ if (view->transient_parent) {
+ XSetTransientForHint(impl->display, impl->win,
+ (Window)(view->transient_parent));
+ }
+
+ XSetLocaleModifiers("");
+ if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
+ XSetLocaleModifiers("@im=");
+ if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
+ fprintf(stderr, "warning: XOpenIM failed\n");
+ }
+ }
+
+ const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
+ if (!(impl->xic = XCreateIC(impl->xim,
+ XNInputStyle, im_style,
+ XNClientWindow, impl->win,
+ XNFocusWindow, impl->win,
+ NULL))) {
+ fprintf(stderr, "warning: XCreateIC failed\n");
+ }
+
+ XFree(vi);
+
+ return 0;
+}
+
+void
+puglShowWindow(PuglView* view)
+{
+ XMapRaised(view->impl->display, view->impl->win);
+ view->visible = true;
+}
+
+void
+puglHideWindow(PuglView* view)
+{
+ XUnmapWindow(view->impl->display, view->impl->win);
+ view->visible = false;
+}
+
+void
+puglDestroy(PuglView* view)
+{
+ if (view) {
+ destroyContext(view);
+ XDestroyWindow(view->impl->display, view->impl->win);
+ XCloseDisplay(view->impl->display);
+ free(view->windowClass);
+ free(view->impl);
+ free(view);
+ }
+}
+
+static PuglKey
+keySymToSpecial(KeySym sym)
+{
+ switch (sym) {
+ case XK_F1: return PUGL_KEY_F1;
+ case XK_F2: return PUGL_KEY_F2;
+ case XK_F3: return PUGL_KEY_F3;
+ case XK_F4: return PUGL_KEY_F4;
+ case XK_F5: return PUGL_KEY_F5;
+ case XK_F6: return PUGL_KEY_F6;
+ case XK_F7: return PUGL_KEY_F7;
+ case XK_F8: return PUGL_KEY_F8;
+ case XK_F9: return PUGL_KEY_F9;
+ case XK_F10: return PUGL_KEY_F10;
+ case XK_F11: return PUGL_KEY_F11;
+ case XK_F12: return PUGL_KEY_F12;
+ case XK_Left: return PUGL_KEY_LEFT;
+ case XK_Up: return PUGL_KEY_UP;
+ case XK_Right: return PUGL_KEY_RIGHT;
+ case XK_Down: return PUGL_KEY_DOWN;
+ case XK_Page_Up: return PUGL_KEY_PAGE_UP;
+ case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
+ case XK_Home: return PUGL_KEY_HOME;
+ case XK_End: return PUGL_KEY_END;
+ case XK_Insert: return PUGL_KEY_INSERT;
+ case XK_Shift_L: return PUGL_KEY_SHIFT;
+ case XK_Shift_R: return PUGL_KEY_SHIFT;
+ case XK_Control_L: return PUGL_KEY_CTRL;
+ case XK_Control_R: return PUGL_KEY_CTRL;
+ case XK_Alt_L: return PUGL_KEY_ALT;
+ case XK_Alt_R: return PUGL_KEY_ALT;
+ case XK_Super_L: return PUGL_KEY_SUPER;
+ case XK_Super_R: return PUGL_KEY_SUPER;
+ }
+ return (PuglKey)0;
+}
+
+static void
+translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
+{
+ KeySym sym = 0;
+ char* str = (char*)event->key.utf8;
+ memset(str, 0, 8);
+ event->key.filter = XFilterEvent(xevent, None);
+ if (xevent->type == KeyRelease || event->key.filter || !view->impl->xic) {
+ if (XLookupString(&xevent->xkey, str, 7, &sym, NULL) == 1) {
+ event->key.character = str[0];
+ }
+ } else {
+ /* TODO: Not sure about this. On my system, some characters work with
+ Xutf8LookupString but not with XmbLookupString, and some are the
+ opposite. */
+ Status status = 0;
+#ifdef X_HAVE_UTF8_STRING
+ const int n = Xutf8LookupString(
+ view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
+#else
+ const int n = XmbLookupString(
+ view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
+#endif
+ if (n > 0) {
+ event->key.character = puglDecodeUTF8((const uint8_t*)str);
+ }
+ }
+ event->key.special = keySymToSpecial(sym);
+ event->key.keycode = xevent->xkey.keycode;
+}
+
+static unsigned
+translateModifiers(unsigned xstate)
+{
+ unsigned state = 0;
+ state |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0;
+ state |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0;
+ state |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0;
+ state |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0;
+ return state;
+}
+
+static PuglEvent
+translateEvent(PuglView* view, XEvent xevent)
+{
+ PuglEvent event;
+ memset(&event, 0, sizeof(event));
+
+ event.any.view = view;
+ if (xevent.xany.send_event) {
+ event.any.flags |= PUGL_IS_SEND_EVENT;
+ }
+
+ switch (xevent.type) {
+ case ClientMessage: {
+ char* type = XGetAtomName(view->impl->display,
+ xevent.xclient.message_type);
+ if (!strcmp(type, "WM_PROTOCOLS")) {
+ event.type = PUGL_CLOSE;
+ }
+ break;
+ }
+ case ConfigureNotify:
+ event.type = PUGL_CONFIGURE;
+ event.configure.x = xevent.xconfigure.x;
+ event.configure.y = xevent.xconfigure.y;
+ event.configure.width = xevent.xconfigure.width;
+ event.configure.height = xevent.xconfigure.height;
+ break;
+ case Expose:
+ event.type = PUGL_EXPOSE;
+ event.expose.x = xevent.xexpose.x;
+ event.expose.y = xevent.xexpose.y;
+ event.expose.width = xevent.xexpose.width;
+ event.expose.height = xevent.xexpose.height;
+ event.expose.count = xevent.xexpose.count;
+ break;
+ case MotionNotify:
+ event.type = PUGL_MOTION_NOTIFY;
+ event.motion.time = xevent.xmotion.time;
+ event.motion.x = xevent.xmotion.x;
+ event.motion.y = xevent.xmotion.y;
+ event.motion.x_root = xevent.xmotion.x_root;
+ event.motion.y_root = xevent.xmotion.y_root;
+ event.motion.state = translateModifiers(xevent.xmotion.state);
+ event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
+ break;
+ case ButtonPress:
+ if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
+ event.type = PUGL_SCROLL;
+ event.scroll.time = xevent.xbutton.time;
+ event.scroll.x = xevent.xbutton.x;
+ event.scroll.y = xevent.xbutton.y;
+ event.scroll.x_root = xevent.xbutton.x_root;
+ event.scroll.y_root = xevent.xbutton.y_root;
+ event.scroll.state = translateModifiers(xevent.xbutton.state);
+ event.scroll.dx = 0.0;
+ event.scroll.dy = 0.0;
+ switch (xevent.xbutton.button) {
+ case 4: event.scroll.dy = 1.0f; break;
+ case 5: event.scroll.dy = -1.0f; break;
+ case 6: event.scroll.dx = -1.0f; break;
+ case 7: event.scroll.dx = 1.0f; break;
+ }
+ }
+ // nobreak
+ case ButtonRelease:
+ if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
+ event.button.type = ((xevent.type == ButtonPress)
+ ? PUGL_BUTTON_PRESS
+ : PUGL_BUTTON_RELEASE);
+ event.button.time = xevent.xbutton.time;
+ event.button.x = xevent.xbutton.x;
+ event.button.y = xevent.xbutton.y;
+ event.button.x_root = xevent.xbutton.x_root;
+ event.button.y_root = xevent.xbutton.y_root;
+ event.button.state = translateModifiers(xevent.xbutton.state);
+ event.button.button = xevent.xbutton.button;
+ }
+ break;
+ case KeyPress:
+ case KeyRelease:
+ event.type = ((xevent.type == KeyPress)
+ ? PUGL_KEY_PRESS
+ : PUGL_KEY_RELEASE);
+ event.key.time = xevent.xkey.time;
+ event.key.x = xevent.xkey.x;
+ event.key.y = xevent.xkey.y;
+ event.key.x_root = xevent.xkey.x_root;
+ event.key.y_root = xevent.xkey.y_root;
+ event.key.state = translateModifiers(xevent.xkey.state);
+ translateKey(view, &xevent, &event);
+ break;
+ case EnterNotify:
+ case LeaveNotify:
+ event.type = ((xevent.type == EnterNotify)
+ ? PUGL_ENTER_NOTIFY
+ : PUGL_LEAVE_NOTIFY);
+ event.crossing.time = xevent.xcrossing.time;
+ event.crossing.x = xevent.xcrossing.x;
+ event.crossing.y = xevent.xcrossing.y;
+ event.crossing.x_root = xevent.xcrossing.x_root;
+ event.crossing.y_root = xevent.xcrossing.y_root;
+ event.crossing.state = translateModifiers(xevent.xcrossing.state);
+ event.crossing.mode = PUGL_CROSSING_NORMAL;
+ if (xevent.xcrossing.mode == NotifyGrab) {
+ event.crossing.mode = PUGL_CROSSING_GRAB;
+ } else if (xevent.xcrossing.mode == NotifyUngrab) {
+ event.crossing.mode = PUGL_CROSSING_UNGRAB;
+ }
+ break;
+
+ case FocusIn:
+ case FocusOut:
+ event.type = ((xevent.type == FocusIn)
+ ? PUGL_FOCUS_IN
+ : PUGL_FOCUS_OUT);
+ event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
+ break;
+
+ default:
+ break;
+ }
+
+ return event;
+}
+
+void
+puglGrabFocus(PuglView* view)
+{
+ XSetInputFocus(
+ view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
+}
+
+PuglStatus
+puglWaitForEvent(PuglView* view)
+{
+ XEvent xevent;
+ XPeekEvent(view->impl->display, &xevent);
+ return PUGL_SUCCESS;
+}
+
+static void
+merge_draw_events(PuglEvent* dst, const PuglEvent* src)
+{
+ if (!dst->type) {
+ *dst = *src;
+ } else {
+ dst->expose.x = MIN(dst->expose.x, src->expose.x);
+ dst->expose.y = MIN(dst->expose.y, src->expose.y);
+ dst->expose.width = MAX(dst->expose.width, src->expose.width);
+ dst->expose.height = MAX(dst->expose.height, src->expose.height);
+ }
+}
+
+PuglStatus
+puglProcessEvents(PuglView* view)
+{
+ /* Maintain a single expose/configure event to execute after all pending
+ events. This avoids redundant drawing/configuration which prevents a
+ series of window resizes in the same loop from being laggy. */
+ PuglEvent expose_event = { 0 };
+ PuglEvent config_event = { 0 };
+ XEvent xevent;
+ while (XPending(view->impl->display) > 0) {
+ XNextEvent(view->impl->display, &xevent);
+ if (xevent.type == KeyRelease) {
+ // Ignore key repeat if necessary
+ if (view->ignoreKeyRepeat &&
+ XEventsQueued(view->impl->display, QueuedAfterReading)) {
+ XEvent next;
+ XPeekEvent(view->impl->display, &next);
+ if (next.type == KeyPress &&
+ next.xkey.time == xevent.xkey.time &&
+ next.xkey.keycode == xevent.xkey.keycode) {
+ XNextEvent(view->impl->display, &xevent);
+ continue;
+ }
+ }
+ } else if (xevent.type == FocusIn) {
+ XSetICFocus(view->impl->xic);
+ } else if (xevent.type == FocusOut) {
+ XUnsetICFocus(view->impl->xic);
+ }
+
+ // Translate X11 event to Pugl event
+ const PuglEvent event = translateEvent(view, xevent);
+
+ if (event.type == PUGL_EXPOSE) {
+ // Expand expose event to be dispatched after loop
+ merge_draw_events(&expose_event, &event);
+ } else if (event.type == PUGL_CONFIGURE) {
+ // Expand configure event to be dispatched after loop
+ merge_draw_events(&config_event, &event);
+ } else {
+ // Dispatch event to application immediately
+ puglDispatchEvent(view, &event);
+ }
+ }
+
+ if (config_event.type) {
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type == PUGL_CAIRO) {
+ // Resize surfaces/contexts before dispatching
+ view->redisplay = true;
+ cairo_xlib_surface_set_size(view->impl->surface,
+ config_event.configure.width,
+ config_event.configure.height);
+ }
+#ifdef PUGL_HAVE_GL
+ if (view->ctx_type == PUGL_CAIRO_GL) {
+ view->redisplay = true;
+ cairo_surface_destroy(view->impl->surface);
+ view->impl->surface = pugl_cairo_gl_create(
+ &view->impl->cairo_gl,
+ config_event.configure.width,
+ config_event.configure.height,
+ 4);
+ pugl_cairo_gl_configure(&view->impl->cairo_gl,
+ config_event.configure.width,
+ config_event.configure.height);
+ createCairoContext(view);
+ }
+#endif
+#endif
+ puglDispatchEvent(view, (const PuglEvent*)&config_event);
+ }
+
+ if (view->redisplay) {
+ expose_event.expose.type = PUGL_EXPOSE;
+ expose_event.expose.view = view;
+ expose_event.expose.x = 0;
+ expose_event.expose.y = 0;
+ expose_event.expose.width = view->width;
+ expose_event.expose.height = view->height;
+ view->redisplay = false;
+ }
+
+ if (expose_event.type) {
+ puglDispatchEvent(view, (const PuglEvent*)&expose_event);
+ }
+
+ return PUGL_SUCCESS;
+}
+
+void
+puglPostRedisplay(PuglView* view)
+{
+ view->redisplay = true;
+}
+
+PuglNativeWindow
+puglGetNativeWindow(PuglView* view)
+{
+ return view->impl->win;
+}
+
+void*
+puglGetContext(PuglView* view)
+{
+#ifdef PUGL_HAVE_CAIRO
+ if (view->ctx_type & PUGL_CAIRO) {
+ return view->impl->cr;
+ }
+#endif
+ return NULL;
+}
diff --git a/pugl/pugl_cairo_test.c b/pugl/pugl_cairo_test.c
new file mode 100644
index 0000000..c04c785
--- /dev/null
+++ b/pugl/pugl_cairo_test.c
@@ -0,0 +1,205 @@
+/*
+ Copyright 2012-2014 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_cairo_test.c A simple Pugl test that creates a top-level window.
+*/
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <cairo/cairo.h>
+
+#include "pugl/pugl.h"
+
+static int quit = 0;
+static bool entered = false;
+
+typedef struct {
+ int x;
+ int y;
+ int w;
+ int h;
+ bool pressed;
+ const char* label;
+} Button;
+
+static Button toggle_button = { 16, 16, 128, 64, false, "Test" };
+
+static void
+roundedBox(cairo_t* cr, double x, double y, double w, double h)
+{
+ static const double radius = 10;
+ static const double degrees = 3.14159265 / 180.0;
+
+ cairo_new_sub_path(cr);
+ cairo_arc(cr,
+ x + w - radius,
+ y + radius,
+ radius, -90 * degrees, 0 * degrees);
+ cairo_arc(cr,
+ x + w - radius, y + h - radius,
+ radius, 0 * degrees, 90 * degrees);
+ cairo_arc(cr,
+ x + radius, y + h - radius,
+ radius, 90 * degrees, 180 * degrees);
+ cairo_arc(cr,
+ x + radius, y + radius,
+ radius, 180 * degrees, 270 * degrees);
+ cairo_close_path(cr);
+}
+
+static void
+buttonDraw(cairo_t* cr, const Button* but)
+{
+ // Draw base
+ if (but->pressed) {
+ cairo_set_source_rgba(cr, 0.4, 0.9, 0.1, 1);
+ } else {
+ cairo_set_source_rgba(cr, 0.3, 0.5, 0.1, 1);
+ }
+ roundedBox(cr, but->x, but->y, but->w, but->h);
+ cairo_fill_preserve(cr);
+
+ // Draw border
+ cairo_set_source_rgba(cr, 0.4, 0.9, 0.1, 1);
+ cairo_set_line_width(cr, 4.0);
+ cairo_stroke(cr);
+
+ // Draw label
+ cairo_text_extents_t extents;
+ cairo_set_font_size(cr, 32.0);
+ cairo_text_extents(cr, but->label, &extents);
+ cairo_move_to(cr,
+ (but->x + but->w / 2) - extents.width / 2,
+ (but->y + but->h / 2) + extents.height / 2);
+ cairo_set_source_rgba(cr, 0, 0, 0, 1);
+ cairo_show_text(cr, but->label);
+}
+
+static bool
+buttonTouches(const Button* but, double x, double y)
+{
+ return (x >= toggle_button.x && x <= toggle_button.x + toggle_button.w &&
+ y >= toggle_button.y && y <= toggle_button.y + toggle_button.h);
+}
+
+static void
+onDisplay(PuglView* view)
+{
+ cairo_t* cr = puglGetContext(view);
+
+ // Draw background
+ int width, height;
+ puglGetSize(view, &width, &height);
+ if (entered) {
+ cairo_set_source_rgb(cr, 0.1, 0.1, 0.1);
+ } else {
+ cairo_set_source_rgb(cr, 0, 0, 0);
+ }
+ cairo_rectangle(cr, 0, 0, width, height);
+ cairo_fill(cr);
+
+ // Draw button
+ buttonDraw(cr, &toggle_button);
+}
+
+static void
+onClose(PuglView* view)
+{
+ quit = 1;
+}
+
+static void
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ switch (event->type) {
+ case PUGL_KEY_PRESS:
+ if (event->key.character == 'q' ||
+ event->key.character == 'Q' ||
+ event->key.character == PUGL_CHAR_ESCAPE) {
+ quit = 1;
+ }
+ break;
+ case PUGL_BUTTON_PRESS:
+ if (buttonTouches(&toggle_button, event->button.x, event->button.y)) {
+ toggle_button.pressed = !toggle_button.pressed;
+ puglPostRedisplay(view);
+ }
+ break;
+ case PUGL_ENTER_NOTIFY:
+ entered = true;
+ puglPostRedisplay(view);
+ break;
+ case PUGL_LEAVE_NOTIFY:
+ entered = false;
+ puglPostRedisplay(view);
+ break;
+ case PUGL_EXPOSE:
+ onDisplay(view);
+ break;
+ case PUGL_CLOSE:
+ onClose(view);
+ break;
+ default: break;
+ }
+}
+
+int
+main(int argc, char** argv)
+{
+ bool useGL = false;
+ bool ignoreKeyRepeat = false;
+ bool resizable = false;
+ for (int i = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "-h")) {
+ printf("USAGE: %s [OPTIONS]...\n\n"
+ " -g Use OpenGL\n"
+ " -h Display this help\n"
+ " -i Ignore key repeat\n"
+ " -r Resizable window\n", argv[0]);
+ return 0;
+ } else if (!strcmp(argv[i], "-g")) {
+ useGL = true;
+ } else if (!strcmp(argv[i], "-i")) {
+ ignoreKeyRepeat = true;
+ } else if (!strcmp(argv[i], "-r")) {
+ resizable = true;
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ }
+ }
+
+ PuglView* view = puglInit(NULL, NULL);
+ puglInitWindowSize(view, 512, 512);
+ puglInitResizable(view, resizable);
+ puglInitContextType(view, useGL ? PUGL_CAIRO_GL : PUGL_CAIRO);
+
+ puglIgnoreKeyRepeat(view, ignoreKeyRepeat);
+ puglSetEventFunc(view, onEvent);
+
+ puglCreateWindow(view, "Pugl Test");
+ puglShowWindow(view);
+
+ while (!quit) {
+ puglWaitForEvent(view);
+ puglProcessEvents(view);
+ }
+
+ puglDestroy(view);
+ return 0;
+}
diff --git a/pugl/pugl_test.c b/pugl/pugl_test.c
new file mode 100644
index 0000000..367e7a4
--- /dev/null
+++ b/pugl/pugl_test.c
@@ -0,0 +1,261 @@
+/*
+ Copyright 2012-2016 David Robillard <http://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
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+/**
+ @file pugl_test.c A simple Pugl test that creates a top-level window.
+*/
+
+#include <locale.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "pugl/gl.h"
+#include "pugl/pugl.h"
+
+static int quit = 0;
+static float xAngle = 0.0f;
+static float yAngle = 0.0f;
+static float dist = 10.0f;
+
+static const float cubeVertices[] = {
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ 1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+
+ 1.0f, 1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f,
+ -1.0f, -1.0f, -1.0f,
+
+ -1.0f, -1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ 1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ -1.0f, -1.0f, -1.0f,
+
+ -1.0f, 1.0f, 1.0f,
+ -1.0f, -1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, -1.0f,
+
+ 1.0f, -1.0f, -1.0f,
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, 1.0f,
+
+ 1.0f, 1.0f, 1.0f,
+ -1.0f, 1.0f, 1.0f,
+ 1.0f, -1.0f, 1.0f
+};
+
+/** Calculate a projection matrix for a given perspective. */
+static void
+perspective(float* m, float fov, float aspect, float zNear, float zFar)
+{
+ const float h = tan(fov);
+ const float w = h / aspect;
+ const float depth = zNear - zFar;
+ const float q = (zFar + zNear) / depth;
+ const float qn = 2 * zFar * zNear / depth;
+
+ m[0] = w; m[1] = 0; m[2] = 0; m[3] = 0;
+ m[4] = 0; m[5] = h; m[6] = 0; m[7] = 0;
+ m[8] = 0; m[9] = 0; m[10] = q; m[11] = -1;
+ m[12] = 0; m[13] = 0; m[14] = qn; m[15] = 0;
+}
+
+static void
+onReshape(PuglView* view, int width, int height)
+{
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ glViewport(0, 0, width, height);
+
+ float projection[16];
+ perspective(projection, 1.8f, width / (float)height, 1.0, 100.0f);
+ glLoadMatrixf(projection);
+}
+
+static void
+onDisplay(PuglView* view)
+{
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ glTranslatef(0.0f, 0.0f, dist * -1);
+ glRotatef(xAngle, 0.0f, 1.0f, 0.0f);
+ glRotatef(yAngle, 1.0f, 0.0f, 0.0f);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+
+ glVertexPointer(3, GL_FLOAT, 0, cubeVertices);
+ glColorPointer(3, GL_FLOAT, 0, cubeVertices);
+ glDrawArrays(GL_TRIANGLES, 0, 12 * 3);
+
+ glDisableClientState(GL_VERTEX_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+}
+
+static void
+printModifiers(PuglView* view, unsigned mods)
+{
+ fprintf(stderr, "Modifiers:%s%s%s%s\n",
+ (mods & PUGL_MOD_SHIFT) ? " Shift" : "",
+ (mods & PUGL_MOD_CTRL) ? " Ctrl" : "",
+ (mods & PUGL_MOD_ALT) ? " Alt" : "",
+ (mods & PUGL_MOD_SUPER) ? " Super" : "");
+}
+
+static void
+onEvent(PuglView* view, const PuglEvent* event)
+{
+ switch (event->type) {
+ case PUGL_NOTHING:
+ break;
+ case PUGL_CONFIGURE:
+ onReshape(view, event->configure.width, event->configure.height);
+ break;
+ case PUGL_EXPOSE:
+ onDisplay(view);
+ break;
+ case PUGL_CLOSE:
+ quit = 1;
+ break;
+ case PUGL_KEY_PRESS:
+ fprintf(stderr, "Key %u (char %u) press (%s)%s\n",
+ event->key.keycode, event->key.character, event->key.utf8,
+ event->key.filter ? " (filtered)" : "");
+ if (event->key.character == 'q' ||
+ event->key.character == 'Q' ||
+ event->key.character == PUGL_CHAR_ESCAPE) {
+ quit = 1;
+ }
+ break;
+ case PUGL_KEY_RELEASE:
+ fprintf(stderr, "Key %u (char %u) release (%s)%s\n",
+ event->key.keycode, event->key.character, event->key.utf8,
+ event->key.filter ? " (filtered)" : "");
+ break;
+ case PUGL_MOTION_NOTIFY:
+ xAngle = -(int)event->motion.x % 360;
+ yAngle = (int)event->motion.y % 360;
+ puglPostRedisplay(view);
+ break;
+ case PUGL_BUTTON_PRESS:
+ case PUGL_BUTTON_RELEASE:
+ fprintf(stderr, "Mouse %d %s at %f,%f ",
+ event->button.button,
+ (event->type == PUGL_BUTTON_PRESS) ? "down" : "up",
+ event->button.x,
+ event->button.y);
+ printModifiers(view, event->scroll.state);
+ break;
+ case PUGL_SCROLL:
+ fprintf(stderr, "Scroll %f %f %f %f ",
+ event->scroll.x, event->scroll.y, event->scroll.dx, event->scroll.dy);
+ printModifiers(view, event->scroll.state);
+ dist += event->scroll.dy;
+ if (dist < 10.0f) {
+ dist = 10.0f;
+ }
+ puglPostRedisplay(view);
+ break;
+ case PUGL_ENTER_NOTIFY:
+ fprintf(stderr, "Entered\n");
+ break;
+ case PUGL_LEAVE_NOTIFY:
+ fprintf(stderr, "Exited\n");
+ break;
+ case PUGL_FOCUS_IN:
+ fprintf(stderr, "Focus in\n");
+ break;
+ case PUGL_FOCUS_OUT:
+ fprintf(stderr, "Focus out\n");
+ break;
+ }
+}
+
+int
+main(int argc, char** argv)
+{
+ bool ignoreKeyRepeat = false;
+ bool resizable = false;
+ for (int i = 1; i < argc; ++i) {
+ if (!strcmp(argv[i], "-h")) {
+ printf("USAGE: %s [OPTIONS]...\n\n"
+ " -h Display this help\n"
+ " -i Ignore key repeat\n"
+ " -r Resizable window\n", argv[0]);
+ return 0;
+ } else if (!strcmp(argv[i], "-i")) {
+ ignoreKeyRepeat = true;
+ } else if (!strcmp(argv[i], "-r")) {
+ resizable = true;
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", argv[i]);
+ }
+ }
+
+ setlocale(LC_CTYPE, "");
+
+ PuglView* view = puglInit(NULL, NULL);
+ puglInitWindowClass(view, "PuglTest");
+ puglInitWindowSize(view, 512, 512);
+ puglInitWindowMinSize(view, 256, 256);
+ puglInitResizable(view, resizable);
+
+ puglIgnoreKeyRepeat(view, ignoreKeyRepeat);
+ puglSetEventFunc(view, onEvent);
+
+ puglCreateWindow(view, "Pugl Test");
+
+ puglEnterContext(view);
+ glEnable(GL_DEPTH_TEST);
+ glDepthFunc(GL_LESS);
+ glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
+ puglLeaveContext(view, false);
+
+ puglShowWindow(view);
+
+ while (!quit) {
+ puglWaitForEvent(view);
+ puglProcessEvents(view);
+ }
+
+ puglDestroy(view);
+ return 0;
+}
diff --git a/pugl/waf b/pugl/waf
new file mode 100755
index 0000000..2dbb06b
--- /dev/null
+++ b/pugl/waf
Binary files differ
diff --git a/pugl/wscript b/pugl/wscript
new file mode 100644
index 0000000..349ee14
--- /dev/null
+++ b/pugl/wscript
@@ -0,0 +1,203 @@
+#!/usr/bin/env python
+import glob
+import os
+import subprocess
+import sys
+import waflib.Logs as Logs
+import waflib.Options as Options
+import waflib.extras.autowaf as autowaf
+
+# Library and package version (UNIX style major, minor, micro)
+# major increment <=> incompatible changes
+# minor increment <=> compatible changes (additions)
+# micro increment <=> no interface changes
+PUGL_VERSION = '0.2.0'
+PUGL_MAJOR_VERSION = '0'
+
+# Mandatory waf variables
+APPNAME = 'pugl' # Package name for waf dist
+VERSION = PUGL_VERSION # Package version for waf dist
+top = '.' # Source directory
+out = 'build' # Build directory
+
+def options(opt):
+ opt.load('compiler_c')
+ opt.load('compiler_cxx')
+ autowaf.set_options(opt)
+ opt.add_option('--no-gl', action='store_true', default=False, dest='no_gl',
+ help='Do not build OpenGL support')
+ opt.add_option('--no-cairo', action='store_true', default=False, dest='no_cairo',
+ help='Do not build Cairo support')
+ opt.add_option('--test', action='store_true', default=False, dest='build_tests',
+ help='Build unit tests')
+ opt.add_option('--static', action='store_true', default=False, dest='static',
+ help='Build static library')
+ opt.add_option('--shared', action='store_true', default=False, dest='shared',
+ help='Build shared library')
+ opt.add_option('--target', default=None, dest='target',
+ help='Target platform (e.g. "win32" or "darwin")')
+ opt.add_option('--log', action='store_true', default=False, dest='log',
+ help='Print GL information to console')
+ opt.add_option('--grab-focus', action='store_true', default=False, dest='grab_focus',
+ help='Work around reparent keyboard issues by grabbing focus')
+
+def configure(conf):
+ conf.env.TARGET_PLATFORM = Options.options.target or Options.platform
+ conf.load('compiler_c')
+ if conf.env.TARGET_PLATFORM == 'win32':
+ conf.load('compiler_cxx')
+
+ autowaf.configure(conf)
+ autowaf.set_c99_mode(conf)
+ autowaf.display_header('Pugl Configuration')
+
+ if not Options.options.no_gl:
+ # TODO: Portable check for OpenGL
+ conf.define('HAVE_GL', 1)
+ autowaf.define(conf, 'PUGL_HAVE_GL', 1)
+
+ if not Options.options.no_cairo:
+ autowaf.check_pkg(conf, 'cairo',
+ uselib_store = 'CAIRO',
+ atleast_version = '1.0.0',
+ mandatory = False)
+ if conf.is_defined('HAVE_CAIRO'):
+ autowaf.define(conf, 'PUGL_HAVE_CAIRO', 1)
+
+ if Options.options.log:
+ autowaf.define(conf, 'PUGL_VERBOSE', 1)
+
+ # Shared library building is broken on win32 for some reason
+ conf.env['BUILD_TESTS'] = Options.options.build_tests
+ conf.env['BUILD_SHARED'] = (conf.env.TARGET_PLATFORM != 'win32' or
+ Options.options.shared)
+ conf.env['BUILD_STATIC'] = (Options.options.build_tests or
+ Options.options.static)
+
+ autowaf.define(conf, 'PUGL_VERSION', PUGL_VERSION)
+ conf.write_config_header('pugl_config.h', remove=False)
+
+ conf.env['INCLUDES_PUGL'] = ['%s/pugl-%s' % (conf.env['INCLUDEDIR'],
+ PUGL_MAJOR_VERSION)]
+ conf.env['LIBPATH_PUGL'] = [conf.env['LIBDIR']]
+ conf.env['LIB_PUGL'] = ['pugl-%s' % PUGL_MAJOR_VERSION];
+
+ autowaf.display_msg(conf, "OpenGL support", conf.is_defined('HAVE_GL'))
+ autowaf.display_msg(conf, "Cairo support", conf.is_defined('HAVE_CAIRO'))
+ autowaf.display_msg(conf, "Verbose console output", conf.is_defined('PUGL_VERBOSE'))
+ autowaf.display_msg(conf, "Static library", str(conf.env['BUILD_STATIC']))
+ autowaf.display_msg(conf, "Unit tests", str(conf.env['BUILD_TESTS']))
+ print('')
+
+def build(bld):
+ # C Headers
+ includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION
+ bld.install_files(includedir, bld.path.ant_glob('pugl/*.h'))
+ bld.install_files(includedir, bld.path.ant_glob('pugl/*.hpp'))
+
+ # Pkgconfig file
+ autowaf.build_pc(bld, 'PUGL', PUGL_VERSION, PUGL_MAJOR_VERSION, [],
+ {'PUGL_MAJOR_VERSION' : PUGL_MAJOR_VERSION})
+
+ libflags = [ '-fvisibility=hidden' ]
+ framework = []
+ libs = []
+ if bld.env.TARGET_PLATFORM == 'win32':
+ lang = 'cxx'
+ lib_source = ['pugl/pugl_win.cpp']
+ libs = ['opengl32', 'gdi32', 'user32']
+ defines = []
+ elif bld.env.TARGET_PLATFORM == 'darwin':
+ lang = 'c' # Objective C, actually
+ lib_source = ['pugl/pugl_osx.m']
+ framework = ['Cocoa', 'OpenGL']
+ defines = []
+ else:
+ lang = 'c'
+ lib_source = ['pugl/pugl_x11.c']
+ libs = ['X11']
+ defines = []
+ if bld.is_defined('HAVE_GL'):
+ libs += ['GL']
+ if bld.env['MSVC_COMPILER']:
+ libflags = []
+ else:
+ libs += ['m']
+
+ # Shared Library
+ if bld.env['BUILD_SHARED']:
+ obj = bld(features = '%s %sshlib' % (lang, lang),
+ export_includes = ['.'],
+ source = lib_source,
+ includes = ['.', './src'],
+ lib = libs,
+ uselib = ['CAIRO'],
+ framework = framework,
+ name = 'libpugl',
+ target = 'pugl-%s' % PUGL_MAJOR_VERSION,
+ vnum = PUGL_VERSION,
+ install_path = '${LIBDIR}',
+ defines = defines,
+ cflags = libflags + [ '-DPUGL_SHARED',
+ '-DPUGL_INTERNAL' ])
+
+ # Static library
+ if bld.env['BUILD_STATIC']:
+ obj = bld(features = '%s %sstlib' % (lang, lang),
+ export_includes = ['.'],
+ source = lib_source,
+ includes = ['.', './src'],
+ lib = libs,
+ uselib = ['CAIRO'],
+ framework = framework,
+ name = 'libpugl_static',
+ target = 'pugl-%s' % PUGL_MAJOR_VERSION,
+ vnum = PUGL_VERSION,
+ install_path = '${LIBDIR}',
+ defines = defines,
+ cflags = ['-DPUGL_INTERNAL'])
+
+ if bld.env['BUILD_TESTS']:
+ test_libs = libs
+ test_cflags = ['']
+
+ # Test programs
+ progs = []
+ if bld.is_defined('HAVE_GL'):
+ progs += ['pugl_test']
+ if bld.is_defined('HAVE_CAIRO'):
+ progs += ['pugl_cairo_test']
+
+ for prog in progs:
+ obj = bld(features = 'c cprogram',
+ source = '%s.c' % prog,
+ includes = ['.', './src'],
+ use = 'libpugl_static',
+ lib = test_libs,
+ uselib = ['CAIRO'],
+ framework = framework,
+ target = prog,
+ install_path = '',
+ defines = defines,
+ cflags = test_cflags)
+
+ if bld.env['DOCS']:
+ bld(features = 'subst',
+ source = 'Doxyfile.in',
+ target = 'Doxyfile',
+ install_path = '',
+ name = 'Doxyfile',
+ PUGL_VERSION = PUGL_VERSION)
+
+ bld(features = 'doxygen',
+ doxyfile = 'Doxyfile')
+
+def lint(ctx):
+ "checks code for style issues"
+ subprocess.call('cpplint.py --filter=+whitespace/comments,-whitespace/tab,-whitespace/braces,-whitespace/labels,-build/header_guard,-readability/casting,-readability/todo,-build/include src/* pugl/*', shell=True)
+
+# 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)
diff --git a/sherlock.c b/sherlock.c
new file mode 100644
index 0000000..9880a6c
--- /dev/null
+++ b/sherlock.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <sherlock.h>
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &atom_inspector;
+ case 1:
+ return &midi_inspector;
+ case 2:
+ return &osc_inspector;
+ default:
+ return NULL;
+ }
+}
diff --git a/sherlock.h b/sherlock.h
new file mode 100644
index 0000000..524a3a2
--- /dev/null
+++ b/sherlock.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _SHERLOCK_LV2_H
+#define _SHERLOCK_LV2_H
+
+#include <stdint.h>
+
+#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
+#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
+#include "lv2/lv2plug.in/ns/ext/midi/midi.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.h"
+#include "lv2/lv2plug.in/ns/ext/urid/urid.h"
+#include "lv2/lv2plug.in/ns/ext/time/time.h"
+#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
+#include "lv2/lv2plug.in/ns/extensions/units/units.h"
+#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#include <stdio.h>
+#include <props.h>
+
+#define SHERLOCK_URI "http://open-music-kontrollers.ch/lv2/sherlock"
+
+#define SHERLOCK_ATOM_INSPECTOR_URI SHERLOCK_URI"#atom_inspector"
+#define SHERLOCK_ATOM_INSPECTOR_NK_URI SHERLOCK_URI"#atom_inspector_4_nk"
+
+#define SHERLOCK_MIDI_INSPECTOR_URI SHERLOCK_URI"#midi_inspector"
+#define SHERLOCK_MIDI_INSPECTOR_NK_URI SHERLOCK_URI"#midi_inspector_4_nk"
+
+#define SHERLOCK_OSC_INSPECTOR_URI SHERLOCK_URI"#osc_inspector"
+#define SHERLOCK_OSC_INSPECTOR_NK_URI SHERLOCK_URI"#osc_inspector_4_nk"
+
+extern const LV2_Descriptor atom_inspector;
+extern const LV2_Descriptor midi_inspector;
+extern const LV2_Descriptor osc_inspector;
+
+typedef struct _position_t position_t;
+typedef struct _state_t state_t;
+
+struct _position_t {
+ uint64_t offset;
+ uint32_t nsamples;
+};
+
+struct _state_t {
+ int32_t overwrite;
+ int32_t block;
+ int32_t follow;
+};
+
+#define MAX_NPROPS 4
+
+static const props_def_t stat_overwrite = {
+ .property = SHERLOCK_URI"#overwrite",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Bool,
+ .mode = PROP_MODE_STATIC
+};
+
+static const props_def_t stat_block = {
+ .property = SHERLOCK_URI"#block",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Bool,
+ .mode = PROP_MODE_STATIC
+};
+
+static const props_def_t stat_follow = {
+ .property = SHERLOCK_URI"#follow",
+ .access = LV2_PATCH__writable,
+ .type = LV2_ATOM__Bool,
+ .mode = PROP_MODE_STATIC
+};
+
+// there is a bug in LV2 <= 0.10
+#if defined(LV2_ATOM_TUPLE_FOREACH)
+# undef LV2_ATOM_TUPLE_FOREACH
+# define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \
+ for (LV2_Atom* (iter) = lv2_atom_tuple_begin(tuple); \
+ !lv2_atom_tuple_is_end(LV2_ATOM_BODY(tuple), (tuple)->atom.size, (iter)); \
+ (iter) = lv2_atom_tuple_next(iter))
+#endif
+
+#endif // _SHERLOCK_LV2_H
diff --git a/sherlock.ttl b/sherlock.ttl
new file mode 100644
index 0000000..fa081e8
--- /dev/null
+++ b/sherlock.ttl
@@ -0,0 +1,247 @@
+# Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+#
+# This is free software: you can redistribute it and/or modify
+# it under the terms of the Artistic License 2.0 as published by
+# The Perl Foundation.
+#
+# This source is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# Artistic License 2.0 for more details.
+#
+# You should have received a copy of the Artistic License 2.0
+# along the source as a COPYING file. If not, obtain it from
+# http://www.perlfoundation.org/artistic_license_2_0.
+
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix doap: <http://usefulinc.com/ns/doap#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
+@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
+@prefix midi: <http://lv2plug.in/ns/ext/midi#> .
+@prefix time: <http://lv2plug.in/ns/ext/time#> .
+@prefix urid: <http://lv2plug.in/ns/ext/urid#> .
+@prefix patch: <http://lv2plug.in/ns/ext/patch#> .
+@prefix state: <http://lv2plug.in/ns/ext/state#> .
+
+@prefix xpress: <http://open-music-kontrollers.ch/lv2/xpress#> .
+@prefix osc: <http://open-music-kontrollers.ch/lv2/osc#> .
+@prefix lic: <http://opensource.org/licenses/> .
+@prefix omk: <http://open-music-kontrollers.ch/ventosus#> .
+@prefix proj: <http://open-music-kontrollers.ch/lv2/> .
+@prefix sherlock: <http://open-music-kontrollers.ch/lv2/sherlock#> .
+
+osc:Event
+ a rdfs:Class ;
+ rdfs:subClassOf atom:Object ;
+ rdfs:label "OSC Event (Bundle or Message)" .
+
+xpress:Message
+ a rdfs:Class ,
+ rdfs:Datatype ;
+ rdfs:subClassOf atom:Atom .
+
+# Maintainer
+omk:me
+ a foaf:Person ;
+ foaf:name "Hanspeter Portner" ;
+ foaf:mbox <mailto:dev@open-music-kontrollers.ch> ;
+ foaf:homepage <http://open-music-kontrollers.ch> .
+
+# Project
+proj:sherlock
+ a doap:Project ;
+ doap:maintainer omk:me ;
+ doap:name "Sherlock Bundle" .
+
+sherlock:overwrite
+ a lv2:Parameter ;
+ rdfs:label "Overwrite" ;
+ rdfs:comment "Overwrite buffer when maximum number of events reached" ;
+ rdfs:range atom:Bool .
+
+sherlock:block
+ a lv2:Parameter ;
+ rdfs:label "Block" ;
+ rdfs:comment "Block addition of newly received events" ;
+ rdfs:range atom:Bool .
+
+sherlock:follow
+ a lv2:Parameter ;
+ rdfs:label "Follow" ;
+ rdfs:comment "Automatically scroll to and show last added event" ;
+ rdfs:range atom:Bool .
+
+# Atom Inspector Plugin
+sherlock:atom_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock Atom Inspector" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:extensionData state:interface;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ,
+ osc:Event ,
+ xpress:Message ;
+ lv2:index 0 ;
+ lv2:symbol "control_in" ;
+ lv2:name "Control In" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ time:Position ,
+ patch:Message ,
+ osc:Event ,
+ xpress:Message ;
+ lv2:index 1 ;
+ lv2:symbol "control_out" ;
+ lv2:name "Control Out" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ ] ;
+
+ patch:writable
+ sherlock:overwrite,
+ sherlock:block ,
+ sherlock:follow ;
+
+ state:state [
+ sherlock:overwrite false ;
+ sherlock:block false ;
+ sherlock:follow false ;
+ ] .
+
+# MIDI Inspector Plugin
+sherlock:midi_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock MIDI Inspector" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:extensionData state:interface;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ patch:Message ,
+ time:Position ;
+ lv2:index 0 ;
+ lv2:symbol "control_in" ;
+ lv2:name "Control In" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports midi:MidiEvent ,
+ patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "control_out" ;
+ lv2:name "Control Out" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ ] ;
+
+ patch:writable
+ sherlock:overwrite ,
+ sherlock:block ,
+ sherlock:follow ;
+
+ state:state [
+ sherlock:overwrite false ;
+ sherlock:block false ;
+ sherlock:follow false ;
+ ] .
+
+# OSC Inspector Plugin
+sherlock:osc_inspector
+ a lv2:Plugin,
+ lv2:AnalyserPlugin ;
+ doap:name "Sherlock OSC Inspector" ;
+ doap:license lic:Artistic-2.0 ;
+ lv2:project proj:sherlock ;
+ lv2:optionalFeature lv2:isLive, lv2:hardRTCapable ;
+ lv2:requiredFeature urid:map, state:loadDefaultState ;
+ lv2:extensionData state:interface;
+
+ lv2:port [
+ # input event port
+ a lv2:InputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ,
+ patch:Message ,
+ time:Position ;
+ lv2:index 0 ;
+ lv2:symbol "control_in" ;
+ lv2:name "Control In" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output event port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports osc:Event ,
+ patch:Message ;
+ lv2:index 1 ;
+ lv2:symbol "control_out" ;
+ lv2:name "Control Out" ;
+ lv2:designation lv2:control ;
+ ] , [
+ # output notify port
+ a lv2:OutputPort ,
+ atom:AtomPort ;
+ atom:bufferType atom:Sequence ;
+ atom:supports atom:Sequence ;
+ lv2:index 2 ;
+ lv2:symbol "notify" ;
+ lv2:name "Notify" ;
+ ] ;
+
+ patch:writable
+ sherlock:overwrite ,
+ sherlock:block ,
+ sherlock:follow ;
+
+ state:state [
+ sherlock:overwrite false ;
+ sherlock:block false ;
+ sherlock:follow false ;
+ ] .
diff --git a/sherlock_nk.c b/sherlock_nk.c
new file mode 100644
index 0000000..d82c357
--- /dev/null
+++ b/sherlock_nk.c
@@ -0,0 +1,569 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <limits.h>
+
+#include <sherlock.h>
+#include <osc.lv2/util.h>
+
+#define NK_PUGL_IMPLEMENTATION
+#include <sherlock_nk.h>
+
+#define ful 0xff
+#define one 0xbb
+#define two 0x66
+#define non 0x0
+const struct nk_color white = {.r = one, .g = one, .b = one, .a = ful};
+const struct nk_color gray = {.r = two, .g = two, .b = two, .a = ful};
+const struct nk_color yellow = {.r = one, .g = one, .b = non, .a = ful};
+const struct nk_color magenta = {.r = one, .g = two, .b = one, .a = ful};
+const struct nk_color green = {.r = non, .g = one, .b = non, .a = ful};
+const struct nk_color blue = {.r = non, .g = one, .b = one, .a = ful};
+const struct nk_color orange = {.r = one, .g = two, .b = non, .a = ful};
+const struct nk_color violet = {.r = two, .g = two, .b = one, .a = ful};
+const struct nk_color red = {.r = one, .g = non, .b = non, .a = ful};
+
+const char *max_items [5] = {
+ "1k", "2k", "4k", "8k", "16k"
+};
+const int32_t max_values [5] = {
+ 0x400, 0x800, 0x1000, 0x2000, 0x4000
+};
+
+static LV2_Atom_Forge_Ref
+_sink(LV2_Atom_Forge_Sink_Handle handle, const void *buf, uint32_t size)
+{
+ atom_ser_t *ser = handle;
+
+ const LV2_Atom_Forge_Ref ref = ser->offset + 1;
+
+ const uint32_t new_offset = ser->offset + size;
+ if(new_offset > ser->size)
+ {
+ uint32_t new_size = ser->size << 1;
+ while(new_offset > new_size)
+ new_size <<= 1;
+
+ if(!(ser->buf = realloc(ser->buf, new_size)))
+ return 0; // realloc failed
+
+ ser->size = new_size;
+ }
+
+ memcpy(ser->buf + ser->offset, buf, size);
+ ser->offset = new_offset;
+
+ return ref;
+}
+
+static LV2_Atom *
+_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref)
+{
+ atom_ser_t *ser = handle;
+
+ const uint32_t offset = ref - 1;
+
+ return (LV2_Atom *)(ser->buf + offset);
+}
+
+static bool
+_ser_malloc(atom_ser_t *ser, size_t size)
+{
+ ser->size = size;
+ ser->offset = 0;
+ ser->buf = malloc(ser->size);
+
+ return ser->buf != NULL;
+}
+
+static bool
+_ser_realloc(atom_ser_t *ser, size_t size)
+{
+ ser->size = size;
+ ser->offset = 0;
+ ser->buf = realloc(ser->buf, ser->size);
+
+ return ser->buf != NULL;
+}
+
+static void
+_ser_free(atom_ser_t *ser)
+{
+ if(ser->buf)
+ free(ser->buf);
+}
+
+static inline void
+_discover(plughandle_t *handle)
+{
+ atom_ser_t ser;
+
+ if(_ser_malloc(&ser, 512))
+ {
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, &ser);
+ LV2_Atom_Forge_Frame frame;
+
+ lv2_atom_forge_object(&handle->forge, &frame, 0, handle->props.urid.patch_get);
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ handle->write_function(handle->controller, 0, lv2_atom_total_size(ser.atom),
+ handle->event_transfer, ser.atom);
+
+ _ser_free(&ser);
+ }
+}
+
+void
+_toggle(plughandle_t *handle, LV2_URID property, int32_t val, bool is_bool)
+{
+ atom_ser_t ser;
+
+ if(_ser_malloc(&ser, 512))
+ {
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, &ser);
+ LV2_Atom_Forge_Frame frame;
+
+ lv2_atom_forge_object(&handle->forge, &frame, 0, handle->props.urid.patch_set);
+ lv2_atom_forge_key(&handle->forge, handle->props.urid.patch_property);
+ lv2_atom_forge_urid(&handle->forge, property);
+
+ lv2_atom_forge_key(&handle->forge, handle->props.urid.patch_value);
+ if(is_bool)
+ lv2_atom_forge_bool(&handle->forge, val);
+ else
+ lv2_atom_forge_int(&handle->forge, val);
+
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ handle->write_function(handle->controller, 0, lv2_atom_total_size(ser.atom),
+ handle->event_transfer, ser.atom);
+
+ _ser_free(&ser);
+ }
+}
+
+void
+_ruler(struct nk_context *ctx, float line_thickness, struct nk_color color)
+{
+ struct nk_rect bounds = nk_layout_space_bounds(ctx);
+ const enum nk_widget_layout_states states = nk_widget(&bounds, ctx);
+
+ if(states != NK_WIDGET_INVALID)
+ {
+ struct nk_command_buffer *canv= nk_window_get_canvas(ctx);
+ const float x0 = bounds.x;
+ const float y0 = bounds.y + bounds.h/2;
+ const float x1 = bounds.x + bounds.w;
+ const float y1 = y0;
+
+ nk_stroke_line(canv, x0, y0, x1, y1, line_thickness, color);
+ }
+}
+
+void
+_empty(struct nk_context *ctx)
+{
+ nk_text(ctx, NULL, 0, NK_TEXT_RIGHT);
+}
+
+static void
+_clear_items(plughandle_t *handle)
+{
+ if(handle->items)
+ {
+ for(int i = 0; i < handle->n_item; i++)
+ {
+ item_t *itm = handle->items[i];
+
+ if(itm)
+ free(itm);
+ }
+
+ free(handle->items);
+ handle->items = NULL;
+ }
+
+ handle->n_item = 0;
+}
+
+static item_t *
+_append_item(plughandle_t *handle, item_type_t type, size_t sz)
+{
+ handle->items = realloc(handle->items, (handle->n_item + 1)*sizeof(item_t *));
+ handle->items[handle->n_item] = malloc(sizeof(item_t) + sz);
+
+ item_t *itm = handle->items[handle->n_item];
+ itm->type = type;
+
+ handle->n_item += 1;
+
+ return itm;
+}
+
+void
+_clear(plughandle_t *handle)
+{
+ _clear_items(handle);
+ nk_str_clear(&handle->str);
+ handle->selected = NULL;
+ handle->counter = 1;
+}
+
+void
+_post_redisplay(plughandle_t *handle)
+{
+ nk_pugl_post_redisplay(&handle->win);
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+
+ void *parent = NULL;
+ LV2UI_Resize *host_resize = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_URID__unmap))
+ handle->unmap = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__parent))
+ parent = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__resize))
+ host_resize = features[i]->data;
+ }
+
+ if(!handle->map || !handle->unmap)
+ {
+ fprintf(stderr, "LV2 URID extension not supported\n");
+ free(handle);
+ return NULL;
+ }
+ if(!parent)
+ {
+ free(handle);
+ return NULL;
+ }
+
+ handle->event_transfer = handle->map->map(handle->map->handle, LV2_ATOM__eventTransfer);
+ lv2_atom_forge_init(&handle->forge, handle->map);
+ lv2_osc_urid_init(&handle->osc_urid, handle->map);
+
+ handle->write_function = write_function;
+ handle->controller = controller;
+
+ if(!props_init(&handle->props, MAX_NPROPS, plugin_uri, handle->map, handle))
+ {
+ fprintf(stderr, "failed to allocate property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ if( !(handle->urid.overwrite = props_register(&handle->props, &stat_overwrite, &handle->state.overwrite, &handle->stash.overwrite))
+ || !(handle->urid.block = props_register(&handle->props, &stat_block, &handle->state.block, &handle->stash.block))
+ || !(handle->urid.follow = props_register(&handle->props, &stat_follow, &handle->state.follow, &handle->stash.follow)) )
+ {
+ free(handle);
+ return NULL;
+ }
+
+ const char *NK_SCALE = getenv("NK_SCALE");
+ const float scale = NK_SCALE ? atof(NK_SCALE) : 1.f;
+ handle->dy = 20.f * scale;
+
+ nk_pugl_config_t *cfg = &handle->win.cfg;
+ cfg->height = 700 * scale;
+ cfg->resizable = true;
+ cfg->ignore = false;
+ cfg->class = "sherlock_inspector";
+ cfg->title = "Sherlock Inspector";
+ cfg->parent = (intptr_t)parent;
+ cfg->data = handle;
+ if(!strcmp(plugin_uri, SHERLOCK_MIDI_INSPECTOR_URI))
+ {
+ handle->type = SHERLOCK_MIDI_INSPECTOR,
+ cfg->width = 600 * scale;
+ cfg->expose = _midi_inspector_expose;
+ }
+ else if(!strcmp(plugin_uri, SHERLOCK_ATOM_INSPECTOR_URI))
+ {
+ handle->type = SHERLOCK_ATOM_INSPECTOR,
+ cfg->width = 1200 * scale;
+ cfg->expose = _atom_inspector_expose;
+ }
+ else if(!strcmp(plugin_uri, SHERLOCK_OSC_INSPECTOR_URI))
+ {
+ handle->type = SHERLOCK_OSC_INSPECTOR,
+ cfg->width = 600 * scale;
+ cfg->expose = _osc_inspector_expose;
+ }
+
+ char *path;
+ if(asprintf(&path, "%sCousine-Regular.ttf", bundle_path) == -1)
+ path = NULL;
+
+ cfg->font.face = path;
+ cfg->font.size = 13 * scale;
+
+ *(intptr_t *)widget = nk_pugl_init(&handle->win);
+ nk_pugl_show(&handle->win);
+
+ if(path)
+ free(path);
+
+ if(host_resize)
+ host_resize->ui_resize(host_resize->handle, cfg->width, cfg->height);
+
+ _clear(handle);
+ _discover(handle);
+
+ nk_str_init_default(&handle->str);
+
+ handle->sratom = sratom_new(handle->map);
+ sratom_set_pretty_numbers(handle->sratom, false);
+ handle->base_uri = "file:///tmp/base";
+
+ return handle;
+}
+
+static void
+cleanup(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ sratom_free(handle->sratom);
+
+ _clear_items(handle);
+ nk_str_free(&handle->str);
+
+ nk_pugl_hide(&handle->win);
+ nk_pugl_shutdown(&handle->win);
+
+ free(handle);
+}
+
+static void
+_osc_bundle(plughandle_t *handle, const LV2_Atom_Object *obj);
+
+static void
+_osc_packet(plughandle_t *handle, const LV2_Atom_Object *obj)
+{
+ if(lv2_osc_is_message_type(&handle->osc_urid, obj->body.otype))
+ {
+ _append_item(handle, ITEM_TYPE_NONE, 0);
+ }
+ else if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ _append_item(handle, ITEM_TYPE_NONE, 0);
+ _osc_bundle(handle, obj);
+ }
+}
+
+static void
+_osc_bundle(plughandle_t *handle, const LV2_Atom_Object *obj)
+{
+ const LV2_Atom_Object *timetag = NULL;
+ const LV2_Atom_Tuple *items = NULL;
+ lv2_osc_bundle_get(&handle->osc_urid, obj, &timetag, &items);
+
+ LV2_ATOM_TUPLE_FOREACH(items, item)
+ {
+ _osc_packet(handle, (const LV2_Atom_Object *)item);
+ }
+}
+
+static void
+port_event(LV2UI_Handle instance, uint32_t i, uint32_t size, uint32_t urid,
+ const void *buf)
+{
+ plughandle_t *handle = instance;
+
+ if(urid != handle->event_transfer)
+ return;
+
+ switch(i)
+ {
+ case 0:
+ case 1:
+ {
+ atom_ser_t ser;
+
+ if(_ser_malloc(&ser, 512))
+ {
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_set_sink(&handle->forge, _sink, _deref, &ser);
+ LV2_Atom_Forge_Ref ref = lv2_atom_forge_sequence_head(&handle->forge, &frame, 0);
+
+ if(props_advance(&handle->props, &handle->forge, 0, buf, &ref))
+ nk_pugl_post_redisplay(&handle->win);
+
+ lv2_atom_forge_pop(&handle->forge, &frame);
+
+ _ser_free(&ser);
+ }
+
+ break;
+ }
+ case 2:
+ {
+ const bool overflow = handle->n_item > MAX_LINES;
+
+ if(overflow && handle->state.overwrite)
+ {
+ _clear(handle);
+ }
+
+ if(overflow || handle->state.block)
+ {
+ break;
+ }
+
+ const LV2_Atom *atom = buf;
+ const LV2_Atom_Tuple *tup = (const LV2_Atom_Tuple *)atom;
+ const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup);
+ const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom);
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom);
+
+ // append frame
+ {
+ item_t *itm = _append_item(handle, ITEM_TYPE_FRAME, 0);
+ itm->frame.offset = offset->body;
+ itm->frame.counter = handle->counter++;
+ itm->frame.nsamples = nsamples->body;
+ }
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, ev)
+ {
+ const size_t ev_sz = sizeof(LV2_Atom_Event) + ev->body.size;
+ item_t *itm = _append_item(handle, ITEM_TYPE_EVENT, ev_sz);
+ memcpy(&itm->event.ev, ev, ev_sz);
+
+ switch(handle->type)
+ {
+ case SHERLOCK_ATOM_INSPECTOR:
+ {
+ if(handle->state.follow)
+ {
+ handle->selected = &itm->event.ev.body;
+ handle->ttl_dirty = true;
+ }
+ } break;
+ case SHERLOCK_OSC_INSPECTOR:
+ {
+ // bundles may span over multiple lines
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)&ev->body;
+ if(lv2_osc_is_bundle_type(&handle->osc_urid, obj->body.otype))
+ {
+ _osc_bundle(handle, obj);
+ }
+ } break;
+ case SHERLOCK_MIDI_INSPECTOR:
+ {
+ // sysex messages may span over multiple lines
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(&ev->body);
+ if( (msg[0] == 0xf0) && (ev->body.size > 4) )
+ {
+ for(uint32_t j = 4; j < ev->body.size; j += 4)
+ _append_item(handle, ITEM_TYPE_NONE, 0); // place holder
+ }
+ } break;
+ }
+ }
+
+ nk_pugl_post_redisplay(&handle->win);
+
+ break;
+ }
+ }
+}
+
+static int
+_idle(LV2UI_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ return nk_pugl_process_events(&handle->win);
+}
+
+static const LV2UI_Idle_Interface idle_ext = {
+ .idle = _idle
+};
+
+static const void *
+extension_data(const char *uri)
+{
+ if(!strcmp(uri, LV2_UI__idleInterface))
+ return &idle_ext;
+
+ return NULL;
+}
+
+const LV2UI_Descriptor midi_inspector_nk = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_NK_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+const LV2UI_Descriptor atom_inspector_nk = {
+ .URI = SHERLOCK_ATOM_INSPECTOR_NK_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+const LV2UI_Descriptor osc_inspector_nk = {
+ .URI = SHERLOCK_OSC_INSPECTOR_NK_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = extension_data
+};
+
+#ifdef _WIN32
+__declspec(dllexport)
+#else
+__attribute__((visibility("default")))
+#endif
+const LV2UI_Descriptor*
+lv2ui_descriptor(uint32_t index)
+{
+ switch(index)
+ {
+ case 0:
+ return &midi_inspector_nk;
+ case 1:
+ return &atom_inspector_nk;
+ case 2:
+ return &osc_inspector_nk;
+
+ default:
+ return NULL;
+ }
+}
diff --git a/sherlock_nk.h b/sherlock_nk.h
new file mode 100644
index 0000000..0ec23cf
--- /dev/null
+++ b/sherlock_nk.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2015-2016 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#ifndef _SHERLOCK_NK_H
+#define _SHERLOCK_NK_H
+
+#include "nk_pugl/nk_pugl.h"
+
+#include <osc.lv2/osc.h>
+#include <sratom/sratom.h>
+
+#define MAX_LINES 2048
+
+typedef enum _plugin_type_t plugin_type_t;
+typedef enum _item_type_t item_type_t;
+typedef struct _item_t item_t;
+typedef struct _plughandle_t plughandle_t;
+typedef struct _atom_ser_t atom_ser_t;
+
+enum _item_type_t {
+ ITEM_TYPE_NONE,
+ ITEM_TYPE_FRAME,
+ ITEM_TYPE_EVENT
+};
+
+struct _item_t {
+ item_type_t type;
+
+ union {
+ struct {
+ int64_t offset;
+