├── .github └── workflows │ ├── linux.yml │ ├── mac.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── NuGet.cmake ├── demo └── panner │ ├── CMakeLists.txt │ ├── include │ ├── plugcontroller.h │ ├── plugids.h │ ├── plugprocessor.h │ └── version.h │ ├── resource │ ├── A2EAF7DB320640F48EDE380DDF89562C_snapshot.png │ ├── A2EAF7DB320640F48EDE380DDF89562C_snapshot_2.0x.png │ ├── background.png │ ├── index.html │ ├── info.rc │ ├── main.js │ └── style.css │ └── source │ ├── plugcontroller.cpp │ ├── plugfactory.cpp │ └── plugprocessor.cpp ├── include └── vstwebview │ ├── bindings.h │ ├── webview.h │ ├── webview_controller_bindings.h │ ├── webview_message_listener.h │ └── webview_pluginview.h └── src └── vstwebview ├── gtk └── webview_gtk.cc ├── osx └── webview_osx.mm ├── webview.cc ├── webview_controller_bindings.cc ├── webview_message_listener.cc ├── webview_pluginview.cc └── win32 ├── webview_edge_chromium.cc ├── webview_edge_chromium.h ├── webview_win32.cc └── webview_win32.h /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | name: LinuxBuild 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Update packages 21 | run: sudo apt-get update 22 | 23 | - name: Install prerequisites 24 | run: sudo apt-get install -y libwebkit2gtk-4.1-dev libx11-xcb-dev libxcb-util-dev libxcb-cursor-dev libxcb-xkb-dev libxkbcommon-dev libxkbcommon-x11-dev libfontconfig1-dev libcairo2-dev libgtkmm-3.0-dev libsqlite3-dev libxcb-keysyms1-dev libtbb-dev 25 | 26 | - name: Configure CMake 27 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_DEMO=ON 28 | 29 | - name: Build 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target panner 31 | 32 | - name: Test 33 | working-directory: ${{github.workspace}}/build 34 | # Execute tests defined by the CMake configuration. 35 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 36 | run: ctest -C ${{env.BUILD_TYPE}} 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/mac.yml: -------------------------------------------------------------------------------- 1 | name: MacBuild 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | runs-on: macos-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Configure CMake 21 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_DEMO=ON 22 | 23 | - name: Build 24 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target panner 25 | 26 | - name: Test 27 | working-directory: ${{github.workspace}}/build 28 | # Execute tests defined by the CMake configuration. 29 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 30 | run: ctest -C ${{env.BUILD_TYPE}} 31 | 32 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: WindowsBuild 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: nuget/setup-nuget@v1 20 | with: 21 | nuget-api-key: ${{ secrets.NuGetAPIKey }} 22 | nuget-version: '5.x' 23 | 24 | - name: Configure CMake 25 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_DEMO=ON 26 | 27 | - name: Build 28 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target panner 29 | 30 | - name: Test 31 | working-directory: ${{github.workspace}}/build 32 | # Execute tests defined by the CMake configuration. 33 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 34 | run: ctest -C ${{env.BUILD_TYPE}} 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21.0) 2 | 3 | set(CMAKE_CXX_STANDARD 20) 4 | set(ABSL_PROPAGATE_CXX_STD ON) 5 | 6 | project(vstwebview) 7 | 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") 9 | 10 | # Third party packages are pulled in via FetchContent 11 | include(FetchContent) 12 | 13 | # We don't want VSTGUI to bloat the binary. 14 | set(SMTG_ADD_VSTGUI OFF) 15 | set(SMTG_ADD_VST3_PLUGINS_SAMPLES OFF) 16 | set(SMTG_ADD_VST3_HOSTING_SAMPLES OFF) 17 | 18 | # VST3SDK 19 | FetchContent_Declare( 20 | vst3sdk 21 | GIT_REPOSITORY https://github.com/steinbergmedia/vst3sdk.git 22 | GIT_SHALLOW 1 23 | ) 24 | FetchContent_MakeAvailable(vst3sdk) 25 | 26 | # json 27 | FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.10.5/json.tar.xz) 28 | FetchContent_MakeAvailable(json) 29 | 30 | smtg_enable_vst3_sdk() 31 | 32 | # Platform specific sources and compile options. 33 | IF (WIN32) 34 | set(WEBVIEW_PLATFORM_SOURCES 35 | src/vstwebview/win32/webview_win32.h 36 | src/vstwebview/win32/webview_win32.cc 37 | src/vstwebview/win32/webview_edge_chromium.h 38 | src/vstwebview/win32/webview_edge_chromium.cc) 39 | 40 | # Needed to shut MSVC up about experimental/coroutine and experimental/resumable even though 41 | # we are not directly using them and they're coming through somehow from Edge 42 | add_compile_options(/await) 43 | elseif (UNIX) 44 | if (NOT APPLE) 45 | set(WEBVIEW_PLATFORM_SOURCES src/vstwebview/gtk/webview_gtk.cc) 46 | find_package(PkgConfig REQUIRED) 47 | pkg_check_modules(GTK3 REQUIRED gtk+-3.0) 48 | pkg_check_modules(WEBKIT2GTK REQUIRED webkit2gtk-4.1) 49 | else() 50 | # TODO any package checking for OSX 51 | add_compile_options(-Wno-suggest-override) 52 | set(WEBVIEW_PLATFORM_SOURCES src/vstwebview/osx/webview_osx.mm) 53 | endif () 54 | endif () 55 | 56 | add_library(vstwebview 57 | ${WEBVIEW_PLATFORM_SOURCES} 58 | src/vstwebview/webview_controller_bindings.cc 59 | src/vstwebview/webview_message_listener.cc 60 | src/vstwebview/webview_pluginview.cc 61 | src/vstwebview/webview.cc) 62 | 63 | target_compile_options(vstwebview PUBLIC "$<$:-DDEVELOPMENT>") 64 | target_compile_options(vstwebview PUBLIC "$<$:-DRELEASE>") 65 | target_include_directories(vstwebview PRIVATE ./src ${vstsdk_SOURCE_DIR} ${json_SOURCE_DIR}/include) 66 | target_include_directories(vstwebview PUBLIC ./include) 67 | add_dependencies(vstwebview nlohmann_json::nlohmann_json) 68 | 69 | # Needed for webview2.h & friends on Win32. 70 | if (WIN32) 71 | option(WIL_BUILD_PACKAGING "" OFF) 72 | option(WIL_BUILD_TESTS "" OFF) 73 | FetchContent_Declare(wil GIT_REPOSITORY "https://github.com/microsoft/wil") 74 | FetchContent_MakeAvailable(wil) 75 | 76 | include(NuGet) 77 | 78 | nuget_add(WebView2 "Microsoft.Web.WebView2" 1.0.1150.38) 79 | nuget_add(CppWinRT "Microsoft.Windows.CppWinRT" 2.0.220315.1) 80 | 81 | target_compile_definitions(vstwebview PRIVATE UNICODE=1 _UNICODE=1) 82 | target_include_directories(vstwebview PRIVATE ${WebView2_PATH}/build/native/include) 83 | 84 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 85 | target_link_libraries(vstwebview PRIVATE ${WebView2_PATH}/build/native/x64/WebView2LoaderStatic.lib) 86 | else () 87 | target_link_libraries(vstwebview PRIVATE ${WebView2_PATH}/build/native/x86/WebView2LoaderStatic.lib) 88 | endif () 89 | 90 | target_link_libraries(vstwebview PRIVATE WIL::WIL Dwmapi Shlwapi) 91 | elseif(UNIX) 92 | 93 | # GTK on Linux 94 | if (NOT APPLE) 95 | target_include_directories(vstwebview PRIVATE ${GTK3_INCLUDE_DIRS} ${WEBKIT2GTK_INCLUDE_DIRS} ${GTKMM3_INCLUDE_DIRS}) 96 | target_link_libraries(vstwebview PRIVATE ${GTK3_LIBRARIES} ${WEBKIT2GTK_LIBRARIES} ${GTKMM3_LIBRARIES}) 97 | else() 98 | target_link_libraries(vstwebview "-framework Foundation") 99 | endif() 100 | endif () 101 | 102 | option(BUILD_DEMO "Build demos" OFF) 103 | if(BUILD_DEMO) 104 | add_subdirectory(demo/panner) 105 | endif() 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ryan Daum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vstwebview 2 | 3 | This package allows one to write VST3 plugin user interfaces using an embedded webview, allowing HTML/CSS/JavaScript 4 | UIs instead of Steinberg's bespoke VSTGUI framework. 5 | 6 | It includes JS binding facilities for piping EditController functionality through to the web layer. 7 | 8 | So: Build your plugin in C++, and then use vstwebview to write your UI layer. 9 | 10 | ## Why? 11 | 12 | VSTGUI is Steinberg's answer to the need for a cross platform UI framework for developing VST3 plugins. It has its own implementation of a bunch of standard controls, its own UI markup language, and its own WYSIWYG editing tool. 13 | 14 | However it's all bespoke, non-standard, non-native, and is missing a lot of common UI things. It has no accessibility aspects at all. One inevitably has to write custom components. And when one does that one ends up having to use Steinberg's custom cross-platform graphics drawing library. 15 | 16 | And then frustration arises. 17 | 18 | Frankly, in this day and age, despite its numerous warts, browser tech is arguably the most crossplatform UI framework. 19 | 20 | Using an embedded webview is: 21 | 22 | * Performant 23 | * Maintained 24 | * Standard / lots of people know it 25 | * Plenty of JS etc. framework options 26 | * Actually supports accessibility. 27 | 28 | (aside ... In my past life @ Google, I worked a bit in the Chromium codebase and I've seen how the sausage is made. I personally think browser tech is solid miracle engineering at this point.) 29 | 30 | Implementations exist for all 3 supported VST3 SDK platform: 31 | * Win32 using Edge/Chromium 32 | * Linux (using webkit2 GTK 4.1) 33 | * and OS X (embedded Safari/WebKit) 34 | 35 | In the demo/ directory I have ported the 'panner' VST3 sample VST as a very minimal and ugly example of how things work. 36 | 37 | More elaborate UIs will generally require a JS framework of some kind, which generally will involve integrating NPM, 38 | etc. into your build. I will leave this as an exercise to the reader. 39 | 40 | For my own synthesizer, I integrated NPM and TypeScript into my CMake build and built a whole UI around this and created a series of TypeScript bindings to wrap the parameter model in VST3. As I clean this up and improve it, I will likely bring some of that over to this repository. 41 | 42 | All of this is a bit early and rough. Contributions and testing welcome. 43 | -------------------------------------------------------------------------------- /cmake/NuGet.cmake: -------------------------------------------------------------------------------- 1 | function(nuget_add OUTPUT_NAME NAME VERSION) 2 | find_program(NUGET nuget) 3 | 4 | if (NUGET STREQUAL "NUGET-NOTFOUND") 5 | if (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/tools/nuget-cli.exe") 6 | message(STATUS "[NuGet] NuGet-CLI not found, downloading...") 7 | file(DOWNLOAD "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" "${CMAKE_CURRENT_BINARY_DIR}/tools/nuget-cli.exe") 8 | endif() 9 | set(NUGET "${CMAKE_CURRENT_BINARY_DIR}/tools/nuget-cli.exe") 10 | endif() 11 | 12 | execute_process(COMMAND ${NUGET} install ${NAME} -Version ${VERSION} -ExcludeVersion -OutputDirectory ${CMAKE_CURRENT_BINARY_DIR}/packages OUTPUT_QUIET COMMAND_ERROR_IS_FATAL ANY) 13 | set(${OUTPUT_NAME}_PATH "${CMAKE_CURRENT_BINARY_DIR}/packages/${NAME}" PARENT_SCOPE) 14 | endfunction() 15 | -------------------------------------------------------------------------------- /demo/panner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15.0) 2 | 3 | project(panner-webview 4 | VERSION 0.1 5 | DESCRIPTION "Steinberg VST 3 Panner example using vstwebview" 6 | ) 7 | 8 | # The samples were useful for looking at, but now they're bloating the build dir. 9 | set(SMTG_ADD_VST3_PLUGINS_SAMPLES OFF) 10 | set(SMTG_ADD_VST3_HOSTING_SAMPLES OFF) 11 | 12 | set(SMTG_CREATE_PLUGIN_LINK ON) 13 | 14 | # If validation is on then the execution of the host in debug seems to fail 15 | set(SMTG_RUN_VST_VALIDATOR OFF) 16 | 17 | # We don't want VSTGUI to bloat the binary. 18 | set(SMTG_ADD_VSTGUI OFF) 19 | 20 | set(BUILD_SHARED_LIBS FALSE) 21 | 22 | # Abseil link on Linux will fail without this. 23 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 24 | 25 | if (UNIX) 26 | add_compile_options(-Wno-suggest-override) 27 | if (NOT APPLE) 28 | set(PLATFORM_LIBRARIES tbb) 29 | endif() 30 | endif() 31 | 32 | smtg_enable_vst3_sdk() 33 | 34 | smtg_add_vst3plugin(panner 35 | include/plugcontroller.h 36 | include/plugids.h 37 | include/plugprocessor.h 38 | include/version.h 39 | source/plugfactory.cpp 40 | source/plugcontroller.cpp 41 | source/plugprocessor.cpp 42 | ) 43 | 44 | smtg_target_add_plugin_resources(panner 45 | RESOURCES 46 | resource/background.png 47 | resource/index.html 48 | resource/style.css 49 | resource/main.js 50 | ) 51 | 52 | smtg_target_add_plugin_snapshots(panner 53 | RESOURCES 54 | resource/A2EAF7DB320640F48EDE380DDF89562C_snapshot.png 55 | resource/A2EAF7DB320640F48EDE380DDF89562C_snapshot_2.0x.png 56 | ) 57 | 58 | 59 | target_link_libraries(panner 60 | PRIVATE 61 | vstwebview 62 | sdk 63 | nlohmann_json::nlohmann_json 64 | ${PLATFORM_LIBRARIES} 65 | ) -------------------------------------------------------------------------------- /demo/panner/include/plugcontroller.h: -------------------------------------------------------------------------------- 1 | ///------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : plugcontroller.h 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #pragma once 38 | 39 | #include "public.sdk/source/vst/vsteditcontroller.h" 40 | #include "pluginterfaces/vst/ivstparameterfunctionname.h" 41 | 42 | #include "vstwebview/webview_controller_bindings.h" 43 | #include "vstwebview/webview_pluginview.h" 44 | 45 | namespace Steinberg { 46 | namespace Panner { 47 | 48 | //----------------------------------------------------------------------------- 49 | class PlugController : public Vst::EditControllerEx1, 50 | public Vst::IParameterFunctionName 51 | { 52 | public: 53 | //------------------------------------------------------------------------ 54 | // create function required for plug-in factory, 55 | // it will be called to create new instances of this controller 56 | //------------------------------------------------------------------------ 57 | static FUnknown* createInstance (void*) 58 | { 59 | return (Vst::IEditController*)new PlugController (); 60 | } 61 | 62 | 63 | //---from IPluginBase-------- 64 | tresult PLUGIN_API initialize (FUnknown* context) SMTG_OVERRIDE; 65 | 66 | //---from EditController----- 67 | IPlugView* PLUGIN_API createView (const char* name) SMTG_OVERRIDE; 68 | tresult PLUGIN_API setComponentState (IBStream* state) SMTG_OVERRIDE; 69 | 70 | //---from IParameterFunctionName---- 71 | tresult PLUGIN_API getParameterIDFromFunctionName (Vst::UnitID unitID, FIDString functionName, 72 | Vst::ParamID& paramID) override; 73 | 74 | Steinberg::ViewRect view_rect_{0, 0, 350, 120}; 75 | std::unique_ptr 76 | webview_controller_bindings_; 77 | vstwebview::WebviewPluginView *webview_pluginview_; 78 | 79 | //---Interface--------- 80 | DEFINE_INTERFACES 81 | DEF_INTERFACE(IUnitInfo) 82 | // Here you can add more supported VST3 interfaces 83 | // DEF_INTERFACE (Vst::IXXX) 84 | DEF_INTERFACE (Vst::IParameterFunctionName) 85 | END_DEFINE_INTERFACES(EditController) 86 | DELEGATE_REFCOUNT(EditController) 87 | }; 88 | 89 | //------------------------------------------------------------------------ 90 | } // namespace Panner 91 | } // namespace Steinberg 92 | -------------------------------------------------------------------------------- /demo/panner/include/plugids.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : plugids.h 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #pragma once 38 | 39 | namespace Steinberg { 40 | namespace Panner { 41 | 42 | // HERE are defined the parameter Ids which are exported to the host 43 | enum PannerParams : Vst::ParamID 44 | { 45 | kBypassId = 100, 46 | 47 | kParamPanId = 102, 48 | }; 49 | 50 | 51 | // HERE you have to define new unique class ids: for processor and for controller 52 | // you can use GUID creator tools like https://www.guidgenerator.com/ 53 | static const FUID MyProcessorUID (0xA2EAF7DB, 0x320640F4, 0x8EDE380D, 0xDF89562C); 54 | static const FUID MyControllerUID (0x239F80C2, 0x4F1442C4, 0x8B58AE6E, 0x7C8644EB); 55 | 56 | //------------------------------------------------------------------------ 57 | } // namespace Panner 58 | } // namespace Steinberg 59 | -------------------------------------------------------------------------------- /demo/panner/include/plugprocessor.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : plugprocessor.h 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #pragma once 38 | 39 | #include "public.sdk/source/vst/vstaudioeffect.h" 40 | 41 | namespace Steinberg { 42 | namespace Panner { 43 | 44 | //----------------------------------------------------------------------------- 45 | class PlugProcessor : public Vst::AudioEffect 46 | { 47 | public: 48 | PlugProcessor (); 49 | 50 | tresult PLUGIN_API initialize (FUnknown* context) SMTG_OVERRIDE; 51 | tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, int32 numIns, 52 | Vst::SpeakerArrangement* outputs, 53 | int32 numOuts) SMTG_OVERRIDE; 54 | 55 | tresult PLUGIN_API canProcessSampleSize (int32 symbolicSampleSize) SMTG_OVERRIDE; 56 | tresult PLUGIN_API setupProcessing (Vst::ProcessSetup& setup) SMTG_OVERRIDE; 57 | tresult PLUGIN_API setActive (TBool state) SMTG_OVERRIDE; 58 | tresult PLUGIN_API process (Vst::ProcessData& data) SMTG_OVERRIDE; 59 | 60 | //------------------------------------------------------------------------ 61 | tresult PLUGIN_API setState (IBStream* state) SMTG_OVERRIDE; 62 | tresult PLUGIN_API getState (IBStream* state) SMTG_OVERRIDE; 63 | 64 | static FUnknown* createInstance (void*) { return (Vst::IAudioProcessor*)new PlugProcessor (); } 65 | 66 | enum 67 | { 68 | kPanLawEqualPower = 0, 69 | }; 70 | 71 | protected: 72 | void getStereoPanCoef (int32 panType, float pan, float& left, float& right) const; 73 | 74 | template 75 | tresult processAudio (Vst::ProcessData& data); 76 | tresult (PlugProcessor::*processAudioPtr) (Vst::ProcessData& data); 77 | 78 | Vst::ParamValue mPanValue = 0; 79 | 80 | bool mBypass = false; 81 | }; 82 | 83 | //------------------------------------------------------------------------ 84 | } // namespace Panner 85 | } // namespace Steinberg 86 | -------------------------------------------------------------------------------- /demo/panner/include/version.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : version.h 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #pragma once 38 | 39 | #include "pluginterfaces/base/fplatform.h" 40 | 41 | #define FULL_VERSION_STR "0.1" 42 | 43 | // HERE you have to define your plug-in, company name, email and web 44 | #define stringPluginName "Panner" 45 | 46 | #define stringOriginalFilename "Panner.vst3" 47 | #if SMTG_PLATFORM_64 48 | #define stringFileDescription stringPluginName" VST3-SDK (64Bit)" 49 | #else 50 | #define stringFileDescription stringPluginName" VST3-SDK" 51 | #endif 52 | #define stringCompanyWeb "http://www.steinberg.net" 53 | #define stringCompanyEmail "mailto:info@steinberg.de" 54 | #define stringCompanyName "Steinberg Media Technologies" 55 | #define stringLegalCopyright "� 2022 Steinberg Media Technologies" 56 | #define stringLegalTrademarks "VST is a trademark of Steinberg Media Technologies GmbH" 57 | -------------------------------------------------------------------------------- /demo/panner/resource/A2EAF7DB320640F48EDE380DDF89562C_snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdaum/vstwebview/9df112ddb8d7bed7d98c2d1a132d51ce41ee1b40/demo/panner/resource/A2EAF7DB320640F48EDE380DDF89562C_snapshot.png -------------------------------------------------------------------------------- /demo/panner/resource/A2EAF7DB320640F48EDE380DDF89562C_snapshot_2.0x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdaum/vstwebview/9df112ddb8d7bed7d98c2d1a132d51ce41ee1b40/demo/panner/resource/A2EAF7DB320640F48EDE380DDF89562C_snapshot_2.0x.png -------------------------------------------------------------------------------- /demo/panner/resource/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdaum/vstwebview/9df112ddb8d7bed7d98c2d1a132d51ce41ee1b40/demo/panner/resource/background.png -------------------------------------------------------------------------------- /demo/panner/resource/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Panner VSTWebview 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 |
12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /demo/panner/resource/info.rc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../include/version.h" 3 | 4 | #define APSTUDIO_READONLY_SYMBOLS 5 | 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // Version 8 | ///////////////////////////////////////////////////////////////////////////// 9 | VS_VERSION_INFO VERSIONINFO 10 | FILEVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT 11 | PRODUCTVERSION MAJOR_VERSION_INT,SUB_VERSION_INT,RELEASE_NUMBER_INT,BUILD_NUMBER_INT 12 | FILEFLAGSMASK 0x3fL 13 | #ifdef _DEBUG 14 | FILEFLAGS 0x1L 15 | #else 16 | FILEFLAGS 0x0L 17 | #endif 18 | FILEOS 0x40004L 19 | FILETYPE 0x1L 20 | FILESUBTYPE 0x0L 21 | BEGIN 22 | BLOCK "StringFileInfo" 23 | BEGIN 24 | BLOCK "040004e4" 25 | BEGIN 26 | VALUE "FileVersion", FULL_VERSION_STR 27 | VALUE "ProductVersion", FULL_VERSION_STR 28 | VALUE "OriginalFilename", stringOriginalFilename 29 | VALUE "FileDescription", stringFileDescription 30 | VALUE "InternalName", stringFileDescription 31 | VALUE "ProductName", stringFileDescription 32 | VALUE "CompanyName", stringCompanyName 33 | VALUE "LegalCopyright", stringLegalCopyright 34 | VALUE "LegalTrademarks", stringLegalTrademarks 35 | //VALUE "PrivateBuild", " \0" 36 | //VALUE "SpecialBuild", " \0" 37 | //VALUE "Comments", " \0" 38 | END 39 | END 40 | BLOCK "VarFileInfo" 41 | BEGIN 42 | VALUE "Translation", 0x400, 1252 43 | END 44 | END -------------------------------------------------------------------------------- /demo/panner/resource/main.js: -------------------------------------------------------------------------------- 1 | function bypassClick() { 2 | let bypass = document.getElementById("bypass").checked; 3 | setParamNormalized(100, bypass ? 1 : 0); 4 | console.log("Bypass set: " + bypass); 5 | } 6 | 7 | function panChange() { 8 | let value = document.getElementById("pan").value; 9 | value = value / 100.0; 10 | setParamNormalized(102, value); 11 | console.log("Pan set: " + value); 12 | } -------------------------------------------------------------------------------- /demo/panner/resource/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url("background.png"); 3 | background-color: #cccccc; 4 | } 5 | 6 | .fields { 7 | float: right; 8 | } -------------------------------------------------------------------------------- /demo/panner/source/plugcontroller.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : plugcontroller.cpp 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #include "../include/plugcontroller.h" 38 | #include "../include/plugids.h" 39 | 40 | #include "base/source/fstreamer.h" 41 | #include "pluginterfaces/base/ibstream.h" 42 | #include "pluginterfaces/base/ustring.h" 43 | 44 | namespace Steinberg { 45 | namespace Panner { 46 | 47 | // example of custom parameter (overwriting to and fromString) 48 | //------------------------------------------------------------------------ 49 | class PanParameter : public Vst::Parameter 50 | { 51 | public: 52 | PanParameter (int32 flags, int32 id); 53 | 54 | void toString (Vst::ParamValue normValue, Vst::String128 string) const SMTG_OVERRIDE; 55 | bool fromString (const Vst::TChar* string, Vst::ParamValue& normValue) const SMTG_OVERRIDE; 56 | }; 57 | 58 | //------------------------------------------------------------------------ 59 | // PanParameter Implementation 60 | //------------------------------------------------------------------------ 61 | PanParameter::PanParameter (int32 flags, int32 id) 62 | { 63 | Steinberg::UString (info.title, USTRINGSIZE (info.title)).assign (USTRING ("Pan")); 64 | Steinberg::UString (info.units, USTRINGSIZE (info.units)).assign (USTRING ("")); 65 | 66 | info.flags = flags; 67 | info.id = id; 68 | info.stepCount = 0; 69 | info.defaultNormalizedValue = 0.5f; 70 | info.unitId = Vst::kRootUnitId; 71 | 72 | setNormalized (.5f); 73 | } 74 | 75 | //------------------------------------------------------------------------ 76 | void PanParameter::toString (Vst::ParamValue normValue, Vst::String128 string) const 77 | { 78 | char text[32]; 79 | if (normValue >= 0.505) 80 | { 81 | sprintf (text, "R %d", int32 ((normValue - 0.5f) * 200 + 0.5f)); 82 | } 83 | else if (normValue <= 0.495) 84 | { 85 | sprintf (text, "L %d", int32 ((0.5f - normValue) * 200 + 0.5f)); 86 | } 87 | else 88 | { 89 | strcpy (text, "C"); 90 | } 91 | 92 | Steinberg::UString (string, 128).fromAscii (text); 93 | } 94 | 95 | //------------------------------------------------------------------------ 96 | bool PanParameter::fromString (const Vst::TChar* string, Vst::ParamValue& normValue) const 97 | { 98 | String wrapper ((Vst::TChar*)string); // do not know buffer size here! 99 | if (wrapper.findFirst (STR ("C")) >= 0) 100 | { 101 | normValue = 0.5; 102 | return true; 103 | } 104 | else 105 | { 106 | bool left = wrapper.findFirst (STR ("L")) == 0; 107 | double tmp = 0.0; 108 | if (wrapper.scanFloat (tmp)) 109 | { 110 | if (tmp < 0) 111 | { 112 | left = true; 113 | if (tmp < -100) 114 | tmp = 100; 115 | else 116 | tmp = -tmp; 117 | } 118 | else if (tmp > 100.0) 119 | { 120 | normValue = 1; 121 | return true; 122 | } 123 | if (!left) 124 | normValue = tmp / 200 + 0.5; 125 | else 126 | normValue = 0.5 - tmp / 200; 127 | 128 | return true; 129 | } 130 | } 131 | return false; 132 | } 133 | 134 | //----------------------------------------------------------------------------- 135 | tresult PLUGIN_API PlugController::initialize (FUnknown* context) 136 | { 137 | tresult result = EditController::initialize (context); 138 | if (result == kResultTrue) 139 | { 140 | //---Create Parameters------------ 141 | parameters.addParameter (STR16 ("Bypass"), nullptr, 1, 0, 142 | Vst::ParameterInfo::kCanAutomate | Vst::ParameterInfo::kIsBypass, 143 | PannerParams::kBypassId); 144 | 145 | auto* panParam = new PanParameter (Vst::ParameterInfo::kCanAutomate, PannerParams::kParamPanId); 146 | parameters.addParameter (panParam); 147 | } 148 | return kResultTrue; 149 | } 150 | 151 | //------------------------------------------------------------------------ 152 | IPlugView* PLUGIN_API PlugController::createView (const char* _name) 153 | { 154 | 155 | webview_controller_bindings_ = 156 | std::make_unique(this); 157 | 158 | webview_pluginview_ = 159 | new vstwebview::WebviewPluginView(this, 160 | "Panner", 161 | {webview_controller_bindings_.get()}, 162 | &view_rect_); 163 | return webview_pluginview_; 164 | } 165 | 166 | //------------------------------------------------------------------------ 167 | tresult PLUGIN_API PlugController::getParameterIDFromFunctionName (Vst::UnitID unitID, 168 | FIDString functionName, 169 | Vst::ParamID& paramID) 170 | { 171 | using namespace Vst; 172 | 173 | paramID = kNoParamId; 174 | 175 | if (unitID == kRootUnitId && FIDStringsEqual (functionName, FunctionNameType::kPanPosCenterX)) 176 | paramID = PannerParams::kParamPanId; 177 | 178 | return (paramID != kNoParamId) ? kResultOk : kResultFalse; 179 | } 180 | 181 | //------------------------------------------------------------------------ 182 | tresult PLUGIN_API PlugController::setComponentState (IBStream* state) 183 | { 184 | // we receive the current state of the component (processor part) 185 | // we read our parameters and bypass value... 186 | if (!state) 187 | return kResultFalse; 188 | 189 | IBStreamer streamer (state, kLittleEndian); 190 | 191 | float savedParam1 = 0.f; 192 | if (streamer.readFloat (savedParam1) == false) 193 | return kResultFalse; 194 | setParamNormalized (PannerParams::kParamPanId, savedParam1); 195 | 196 | // read the bypass 197 | int32 bypassState; 198 | if (streamer.readInt32 (bypassState) == false) 199 | return kResultFalse; 200 | setParamNormalized (kBypassId, bypassState ? 1 : 0); 201 | 202 | return kResultOk; 203 | } 204 | 205 | //------------------------------------------------------------------------ 206 | } // namespace 207 | } // namespace Steinberg 208 | -------------------------------------------------------------------------------- /demo/panner/source/plugfactory.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : plugfactory.cpp 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #include "public.sdk/source/main/pluginfactory.h" 38 | 39 | #include "../include/plugcontroller.h" // for createInstance 40 | #include "../include/plugprocessor.h" // for createInstance 41 | #include "../include/plugids.h" // for uids 42 | #include "../include/version.h" // for version and naming 43 | 44 | #define stringSubCategory Vst::PlugType::kSpatialFx // Subcategory for this plug-in (to be changed if needed, see PlugType in ivstaudioprocessor.h) 45 | 46 | BEGIN_FACTORY_DEF (stringCompanyName, stringCompanyWeb, stringCompanyEmail) 47 | 48 | DEF_CLASS2 (INLINE_UID_FROM_FUID(Steinberg::Panner::MyProcessorUID), 49 | PClassInfo::kManyInstances, // cardinality 50 | kVstAudioEffectClass, // the component category (do not changed this) 51 | stringPluginName, // here the plug-in name (to be changed) 52 | Vst::kDistributable, // means that component and controller could be distributed on different computers 53 | stringSubCategory, // Subcategory for this plug-in (to be changed) 54 | FULL_VERSION_STR, // Plug-in version (to be changed) 55 | kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define) 56 | Steinberg::Panner::PlugProcessor::createInstance) // function pointer called when this component should be instantiated 57 | 58 | DEF_CLASS2 (INLINE_UID_FROM_FUID(Steinberg::Panner::MyControllerUID), 59 | PClassInfo::kManyInstances, // cardinality 60 | kVstComponentControllerClass,// the Controller category (do not changed this) 61 | stringPluginName "Controller", // controller name (could be the same than component name) 62 | 0, // not used here 63 | "", // not used here 64 | FULL_VERSION_STR, // Plug-in version (to be changed) 65 | kVstVersionString, // the VST 3 SDK version (do not changed this, use always this define) 66 | Steinberg::Panner::PlugController::createInstance)// function pointer called when this component should be instantiated 67 | 68 | END_FACTORY 69 | 70 | -------------------------------------------------------------------------------- /demo/panner/source/plugprocessor.cpp: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------ 2 | // Project : VST SDK 3 | // 4 | // Category : Examples 5 | // Filename : plugprocessor.cpp 6 | // Created by : Steinberg, 02/2020 7 | // Description : Panner Example for VST 3 8 | // 9 | //----------------------------------------------------------------------------- 10 | // LICENSE 11 | // (c) 2022, Steinberg Media Technologies GmbH, All Rights Reserved 12 | //----------------------------------------------------------------------------- 13 | // Redistribution and use in source and binary forms, with or without modification, 14 | // are permitted provided that the following conditions are met: 15 | // 16 | // * Redistributions of source code must retain the above copyright notice, 17 | // this list of conditions and the following disclaimer. 18 | // * Redistributions in binary form must reproduce the above copyright notice, 19 | // this list of conditions and the following disclaimer in the documentation 20 | // and/or other materials provided with the distribution. 21 | // * Neither the name of the Steinberg Media Technologies nor the names of its 22 | // contributors may be used to endorse or promote products derived from this 23 | // software without specific prior written permission. 24 | // 25 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 26 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28 | // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 29 | // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 30 | // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 33 | // OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | // OF THE POSSIBILITY OF SUCH DAMAGE. 35 | //----------------------------------------------------------------------------- 36 | 37 | #include "../include/plugprocessor.h" 38 | #include "../include/plugids.h" 39 | 40 | #include "public.sdk/source/vst/vstaudioprocessoralgo.h" 41 | #include "base/source/fstreamer.h" 42 | #include "pluginterfaces/base/ibstream.h" 43 | #include "pluginterfaces/vst/ivstparameterchanges.h" 44 | 45 | namespace Steinberg { 46 | namespace Panner { 47 | 48 | #ifndef kPI 49 | #define kPI 3.14159265358979323846 50 | #endif 51 | 52 | #define kRampingTimeMs 10.0 // in ms 53 | 54 | //----------------------------------------------------------------------------- 55 | PlugProcessor::PlugProcessor () 56 | { 57 | // register its editor class 58 | setControllerClass (MyControllerUID); 59 | 60 | // default init 61 | processAudioPtr = &PlugProcessor::processAudio; 62 | } 63 | 64 | //----------------------------------------------------------------------------- 65 | tresult PLUGIN_API PlugProcessor::initialize (FUnknown* context) 66 | { 67 | //---always initialize the parent------- 68 | tresult result = AudioEffect::initialize (context); 69 | if (result != kResultTrue) 70 | return kResultFalse; 71 | 72 | //---create Audio In/Out busses------ 73 | // we want a Mono Input and a Stereo Output 74 | addAudioInput (STR16 ("AudioInput"), Vst::SpeakerArr::kMono); 75 | addAudioOutput (STR16 ("AudioOutput"), Vst::SpeakerArr::kStereo); 76 | 77 | return kResultTrue; 78 | } 79 | 80 | //------------------------------------------------------------------------ 81 | tresult PLUGIN_API PlugProcessor::canProcessSampleSize (int32 symbolicSampleSize) 82 | { 83 | return ((symbolicSampleSize == Vst::kSample32) || (symbolicSampleSize == Vst::kSample64)) ? 84 | kResultTrue : 85 | kResultFalse; 86 | } 87 | 88 | //----------------------------------------------------------------------------- 89 | tresult PLUGIN_API PlugProcessor::setBusArrangements (Vst::SpeakerArrangement* inputs, int32 numIns, 90 | Vst::SpeakerArrangement* outputs, 91 | int32 numOuts) 92 | { 93 | // we only support mono to stereo 94 | if (numIns == 1 && numOuts == 1 && inputs[0] == Vst::SpeakerArr::kMono && 95 | outputs[0] == Vst::SpeakerArr::kStereo) 96 | { 97 | return AudioEffect::setBusArrangements (inputs, numIns, outputs, numOuts); 98 | } 99 | return kResultFalse; 100 | } 101 | 102 | //----------------------------------------------------------------------------- 103 | tresult PLUGIN_API PlugProcessor::setupProcessing (Vst::ProcessSetup& setup) 104 | { 105 | if (setup.symbolicSampleSize == Vst::kSample64) 106 | processAudioPtr = &PlugProcessor::processAudio; 107 | else 108 | processAudioPtr = &PlugProcessor::processAudio; 109 | 110 | return AudioEffect::setupProcessing (setup); 111 | } 112 | 113 | //----------------------------------------------------------------------------- 114 | tresult PLUGIN_API PlugProcessor::setActive (TBool state) 115 | { 116 | return AudioEffect::setActive (state); 117 | } 118 | 119 | //----------------------------------------------------------------------------- 120 | tresult PLUGIN_API PlugProcessor::process (Vst::ProcessData& data) 121 | { 122 | //--- Read inputs parameter changes----------- 123 | if (data.inputParameterChanges) 124 | { 125 | int32 numParamsChanged = data.inputParameterChanges->getParameterCount (); 126 | for (int32 index = 0; index < numParamsChanged; index++) 127 | { 128 | if (Vst::IParamValueQueue* paramQueue = 129 | data.inputParameterChanges->getParameterData (index)) 130 | { 131 | Vst::ParamValue value; 132 | int32 sampleOffset; 133 | int32 numPoints = paramQueue->getPointCount (); 134 | switch (paramQueue->getParameterId ()) 135 | { 136 | case PannerParams::kParamPanId: 137 | if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) == 138 | kResultTrue) 139 | mPanValue = value; 140 | break; 141 | 142 | case PannerParams::kBypassId: 143 | if (paramQueue->getPoint (numPoints - 1, sampleOffset, value) == 144 | kResultTrue) 145 | mBypass = (value > 0.5f); 146 | break; 147 | } 148 | } 149 | } 150 | } 151 | 152 | //--- Process Audio--------------------- 153 | //--- ---------------------------------- 154 | if (data.numInputs == 0 || data.numOutputs == 0 || data.numSamples == 0) 155 | { 156 | // nothing to do 157 | return kResultOk; 158 | } 159 | 160 | return (this->*processAudioPtr) (data); 161 | } 162 | 163 | //------------------------------------------------------------------------ 164 | template 165 | tresult PlugProcessor::processAudio (Vst::ProcessData& data) 166 | { 167 | int32 numFrames = data.numSamples; 168 | 169 | uint32 sampleFramesSize = getSampleFramesSizeInBytes (processSetup, numFrames); 170 | auto** currentInputBuffers = 171 | (SampleType**)Vst::getChannelBuffersPointer (processSetup, data.inputs[0]); 172 | auto** currentOutputBuffers = 173 | (SampleType**)Vst::getChannelBuffersPointer (processSetup, data.outputs[0]); 174 | 175 | // if we have only silence clear the output and do nothing. 176 | data.outputs->silenceFlags = data.inputs->silenceFlags ? 0x7FFFF : 0; 177 | if (data.inputs->silenceFlags) 178 | { 179 | memset (currentOutputBuffers[0], 0, sampleFramesSize); 180 | memset (currentOutputBuffers[1], 0, sampleFramesSize); 181 | 182 | return kResultOk; 183 | } 184 | 185 | float leftPan; 186 | float rightPan; 187 | if (mBypass) 188 | getStereoPanCoef (kPanLawEqualPower, 0.f, leftPan, rightPan); 189 | else 190 | getStereoPanCoef (kPanLawEqualPower, mPanValue, leftPan, rightPan); 191 | 192 | //---pan : 1 -> 2--------------------- 193 | SampleType tmp; 194 | SampleType* inputMono = currentInputBuffers[0]; 195 | SampleType* outputLeft = currentOutputBuffers[0]; 196 | SampleType* outputRight = currentOutputBuffers[1]; 197 | 198 | for (int32 n = 0; n < numFrames; n++) 199 | { 200 | tmp = inputMono[n]; 201 | outputLeft[n] = tmp * leftPan; 202 | outputRight[n] = tmp * rightPan; 203 | } 204 | 205 | return kResultOk; 206 | } 207 | 208 | //------------------------------------------------------------------------ 209 | void PlugProcessor::getStereoPanCoef (int32 panType, float pan, float& left, float& right) const 210 | { 211 | if (panType == kPanLawEqualPower) 212 | { 213 | pan = pan * kPI * 0.5f; 214 | left = cosf (pan); 215 | right = sinf (pan); 216 | } 217 | else 218 | { 219 | left = 0.5f; 220 | right = 0.5f; 221 | } 222 | } 223 | 224 | //------------------------------------------------------------------------ 225 | tresult PLUGIN_API PlugProcessor::setState (IBStream* state) 226 | { 227 | if (!state) 228 | return kResultFalse; 229 | 230 | // called when we load a preset or project, the model has to be reloaded 231 | 232 | IBStreamer streamer (state, kLittleEndian); 233 | 234 | float savedPan= 0.f; 235 | if (streamer.readFloat (savedPan) == false) 236 | return kResultFalse; 237 | 238 | int32 savedBypass = 0; 239 | if (streamer.readInt32 (savedBypass) == false) 240 | return kResultFalse; 241 | 242 | mPanValue = savedPan; 243 | mBypass = savedBypass > 0; 244 | 245 | return kResultOk; 246 | } 247 | 248 | //------------------------------------------------------------------------ 249 | tresult PLUGIN_API PlugProcessor::getState (IBStream* state) 250 | { 251 | // here we need to save the model (preset or project) 252 | 253 | float toSavePan = mPanValue; 254 | int32 toSaveBypass = mBypass ? 1 : 0; 255 | 256 | IBStreamer streamer (state, kLittleEndian); 257 | streamer.writeFloat (toSavePan); 258 | streamer.writeInt32 (toSaveBypass); 259 | 260 | return kResultOk; 261 | } 262 | 263 | //------------------------------------------------------------------------ 264 | } // namespace 265 | } // namespace Steinberg 266 | -------------------------------------------------------------------------------- /include/vstwebview/bindings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Ryan Daum 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | namespace vstwebview { 20 | 21 | class Webview; 22 | 23 | /** 24 | * Interface for bindings implementations. 25 | * Each instance is called on webview initialization, to permit the installation 26 | * of custom JS bindings into the webview. 27 | */ 28 | class Bindings { 29 | public: 30 | virtual void Bind(vstwebview::Webview *webview) = 0; 31 | }; 32 | } // namespace vstwebview 33 | -------------------------------------------------------------------------------- /include/vstwebview/webview.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Ryan Daum 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "pluginterfaces/gui/iplugview.h" 29 | 30 | namespace vstwebview { 31 | 32 | using DispatchFunction = std::function; 33 | 34 | // Abstract webview parent. 35 | class Webview { 36 | public: 37 | virtual ~Webview() = default; 38 | 39 | /** 40 | * Create a JavaScript function ('name') that invokes native function 'f' 41 | * and returns a Promise with its results. 42 | */ 43 | using FunctionBinding = std::function; 45 | void BindFunction(const std::string &name, FunctionBinding f); 46 | 47 | /** 48 | * Unbind a previously-bound JavaScript function. 49 | */ 50 | void UnbindFunction(const std::string &name); 51 | 52 | /** 53 | * Set the webview document title. 54 | */ 55 | virtual void SetTitle(const std::string &title) = 0; 56 | 57 | /* 58 | * Adjust the size of the webview. 59 | */ 60 | enum class SizeHint { 61 | kNone, // Width and height are default size 62 | kMin, // Width and height are minimum bounds 63 | kMax, // Width and height are maximum bounds 64 | kFixed // Window size can not be changed by a user 65 | }; 66 | virtual void SetViewSize(int width, int height, 67 | SizeHint hints = SizeHint::kNone) = 0; 68 | 69 | virtual std::string ContentRootURI() const = 0; 70 | 71 | /** 72 | * Navigate the webview to a URL. 73 | */ 74 | virtual void Navigate(const std::string &url) = 0; 75 | 76 | /* 77 | * Evaluate a fragment of JS in the webview. 78 | */ 79 | using ResultCallback = std::function; 80 | virtual void EvalJS(const std::string &js, ResultCallback rs) = 0; 81 | 82 | /* 83 | * Set a fragment of JS to execute when the webview first loads a document. 84 | */ 85 | virtual void OnDocumentCreate(const std::string &js) = 0; 86 | 87 | /* 88 | * Returns the handle for the platform window hosting the webview. 89 | */ 90 | virtual void *PlatformWindow() const = 0; 91 | 92 | /* 93 | * Terminate the webview execution. 94 | */ 95 | virtual void Terminate() = 0; 96 | 97 | protected: 98 | void OnBrowserMessage(const std::string &msg); 99 | virtual void DispatchIn(DispatchFunction f) = 0; 100 | 101 | private: 102 | void ResolveFunctionDispatch(int seq, int status, 103 | const nlohmann::json &result); 104 | 105 | std::map bindings_; 106 | ; 107 | }; 108 | 109 | using WebviewCreatedCallback = std::function; 110 | std::unique_ptr MakeWebview(bool debug, 111 | Steinberg::IPlugFrame *plug_frame, 112 | void *window, 113 | WebviewCreatedCallback created_cb); 114 | 115 | } // namespace vstwebview 116 | -------------------------------------------------------------------------------- /include/vstwebview/webview_controller_bindings.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Ryan Daum 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include "vstwebview/bindings.h" 25 | #include "vstwebview/webview.h" 26 | 27 | using nlohmann::json; 28 | 29 | namespace vstwebview { 30 | 31 | /** 32 | * Implementation of JS bindings for proxying the functionality of the VST3 33 | * EditController through to the webview. 34 | */ 35 | class WebviewControllerBindings : public vstwebview::Bindings { 36 | public: 37 | explicit WebviewControllerBindings( 38 | Steinberg::Vst::EditControllerEx1 *controller); 39 | 40 | void Bind(vstwebview::Webview *webview) override; 41 | 42 | private: 43 | void DeclareJSBinding(const std::string &name, 44 | vstwebview::Webview::FunctionBinding binding); 45 | 46 | using CallbackFn = json (WebviewControllerBindings::*)( 47 | vstwebview::Webview *webview, const json &); 48 | std::function 50 | BindCallback(CallbackFn fn); 51 | 52 | json GetParameterObject(vstwebview::Webview *webview, const json &in); 53 | json GetParameterObjects(vstwebview::Webview *webview, const json &in); 54 | json SetParameterNormalized(vstwebview::Webview *webview, const json &in); 55 | json NormalizedParamToPlain(vstwebview::Webview *webview, const json &in); 56 | json GetParamNormalized(vstwebview::Webview *webview, const json &in); 57 | json BeginEdit(vstwebview::Webview *webview, const json &in); 58 | json PerformEdit(vstwebview::Webview *webview, const json &in); 59 | json EndEdit(vstwebview::Webview *webview, const json &in); 60 | json GetParameterCount(vstwebview::Webview *webview, const json &in); 61 | json GetSelectedUnit(vstwebview::Webview *webview, const json &in); 62 | json SelectUnit(vstwebview::Webview *webview, const json &in); 63 | json SubscribeParameter(vstwebview::Webview *webview, const json &in); 64 | json DoSendMessage(vstwebview::Webview *webview, const json &in); 65 | 66 | std::unique_ptr thread_checker_; 67 | std::vector> 68 | bindings_; 69 | std::unique_ptr param_dep_proxy_; 70 | Steinberg::Vst::EditControllerEx1 *controller_; 71 | }; 72 | 73 | } // namespace vstwebview -------------------------------------------------------------------------------- /include/vstwebview/webview_message_listener.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Ryan Daum 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | using nlohmann::json; 24 | 25 | namespace vstwebview { 26 | 27 | class Webview; 28 | 29 | class WebviewMessageListener { 30 | public: 31 | explicit WebviewMessageListener(vstwebview::Webview *webview) 32 | : webview_(webview) {} 33 | 34 | struct MessageAttribute { 35 | std::string name; 36 | enum class Type { INT, FLOAT, STRING, BINARY }; 37 | Type type; 38 | }; 39 | 40 | void Subscribe(const std::string &receiver, const std::string &message_id, 41 | const std::vector &attributes); 42 | 43 | Steinberg::tresult Notify(Steinberg::Vst::IMessage *message); 44 | 45 | private: 46 | struct MessageDescriptor { 47 | std::string message_id; 48 | std::vector attributes; 49 | }; 50 | 51 | struct MessageSubscription { 52 | MessageDescriptor descriptor; 53 | std::string notify_function; 54 | }; 55 | 56 | json SerializeMessage(Steinberg::Vst::IMessage *message, 57 | const MessageDescriptor &descriptor); 58 | 59 | std::unordered_map subscriptions_; 60 | vstwebview::Webview *webview_; 61 | }; 62 | 63 | } // namespace vstwebview -------------------------------------------------------------------------------- /include/vstwebview/webview_pluginview.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Ryan Daum 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "vstwebview/bindings.h" 27 | #include "vstwebview/webview.h" 28 | 29 | namespace vstwebview { 30 | 31 | class WebviewControllerBindings; 32 | 33 | /** 34 | * An implementation of the VST3 EditorView which delegates all UI functionality 35 | * through to a platform webview. 36 | */ 37 | class WebviewPluginView : public Steinberg::Vst::EditorView { 38 | public: 39 | WebviewPluginView(Steinberg::Vst::EditController *controller, 40 | const std::string &title, 41 | const std::vector &bindings, 42 | Steinberg::ViewRect *size = nullptr, 43 | const std::string &uri = {}); 44 | 45 | // EditorView overrides 46 | Steinberg::tresult isPlatformTypeSupported( 47 | Steinberg::FIDString type) override; 48 | 49 | void attachedToParent() override; 50 | void removedFromParent() override; 51 | Steinberg::tresult setFrame(Steinberg::IPlugFrame *frame) override; 52 | Steinberg::tresult onFocus(Steinberg::TBool a_bool) override; 53 | 54 | Steinberg::tresult canResize() override; 55 | Steinberg::tresult onSize(Steinberg::ViewRect *newSize) override; 56 | 57 | private: 58 | std::mutex webview_mutex_; 59 | const std::string &title_; 60 | std::unique_ptr webview_handle_; 61 | std::vector bindings_; 62 | std::string uri_; 63 | }; 64 | 65 | } // namespace vstwebview 66 | -------------------------------------------------------------------------------- /src/vstwebview/gtk/webview_gtk.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #define GNU_SOURCE 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "base/source/fobject.h" 12 | #include "vstwebview/webview.h" 13 | 14 | static unsigned int kAddressMarker = 0xcafebabe; 15 | 16 | namespace vstwebview { 17 | 18 | class WebviewWebkitGTK : public Webview, 19 | public Steinberg::Linux::ITimerHandler, 20 | public Steinberg::FObject { 21 | public: 22 | WebviewWebkitGTK(bool debug, Steinberg::IPlugFrame *plug_frame, 23 | Window x11Parent, WebviewCreatedCallback created_callback) { 24 | // On linux the IPlugFrame is also a "run loop" we can use to schedule 25 | // timers and file-descriptor triggered events. 26 | run_loop_ = plug_frame; 27 | 28 | gtk_init_check(nullptr, nullptr); 29 | 30 | window_ = gtk_plug_new(x11Parent); 31 | 32 | g_signal_connect(G_OBJECT(window_), "destroy", 33 | G_CALLBACK(+[](GtkWidget *, gpointer arg) { 34 | static_cast(arg)->Terminate(); 35 | }), 36 | this); 37 | 38 | MakeWebView(debug); 39 | OnDocumentCreate( 40 | "window.external={invoke:function(s){window.webkit.messageHandlers." 41 | "external.postMessage(s);}}"); 42 | 43 | gtk_container_add(GTK_CONTAINER(window_), webview_); 44 | gtk_widget_grab_focus(webview_); 45 | gtk_widget_show_all(window_); 46 | 47 | created_callback(this); 48 | 49 | // Call into GTK main loop check 60 times a second. 50 | run_loop_->registerTimer(this, 16); 51 | } 52 | 53 | std::string ContentRootURI() const override { 54 | std::string resPath; 55 | Dl_info info; 56 | 57 | if (dladdr(&kAddressMarker, &info) != 0) { 58 | auto path = 59 | std::filesystem::absolute(std::filesystem::path(info.dli_fname)); 60 | auto content_path = path.relative_path().parent_path().parent_path(); 61 | return "file:///" + content_path.generic_string() + "/Resources/"; 62 | } 63 | return ""; 64 | } 65 | 66 | void OnDocumentCreate(const std::string &js) override { 67 | WebKitUserContentManager *manager = 68 | webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webview_)); 69 | webkit_user_content_manager_add_script( 70 | manager, 71 | webkit_user_script_new(js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, 72 | WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, 73 | nullptr, nullptr)); 74 | } 75 | 76 | void SetTitle(const std::string &title) override { 77 | gtk_window_set_title(GTK_WINDOW(window_), title.c_str()); 78 | } 79 | 80 | void SetViewSize(int width, int height, SizeHint hints) override { 81 | gtk_window_set_resizable(GTK_WINDOW(window_), hints != SizeHint::kFixed); 82 | if (hints == SizeHint::kNone) { 83 | gtk_window_resize(GTK_WINDOW(window_), width, height); 84 | } else if (hints == SizeHint::kFixed) { 85 | gtk_widget_set_size_request(window_, width, height); 86 | } else { 87 | GdkGeometry g; 88 | g.min_width = g.max_width = width; 89 | g.min_height = g.max_height = height; 90 | GdkWindowHints h = 91 | (hints == SizeHint::kMin ? GDK_HINT_MIN_SIZE : GDK_HINT_MAX_SIZE); 92 | // This defines either MIN_SIZE, or MAX_SIZE, but not both: 93 | gtk_window_set_geometry_hints(GTK_WINDOW(window_), nullptr, &g, h); 94 | } 95 | } 96 | 97 | void Navigate(const std::string &url) override { 98 | webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview_), url.c_str()); 99 | } 100 | 101 | void EvalJS(const std::string &js, ResultCallback rs) override { 102 | webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(webview_), js.c_str(), 103 | nullptr, nullptr, nullptr); 104 | } 105 | 106 | void *PlatformWindow() const override { return window_; } 107 | void Terminate() override { gtk_main_quit(); } 108 | 109 | DELEGATE_REFCOUNT(Steinberg::FObject) 110 | DEFINE_INTERFACES 111 | DEF_INTERFACE(Steinberg::Linux::ITimerHandler) 112 | END_DEFINE_INTERFACES(Steinberg::FObject) 113 | 114 | protected: 115 | void DispatchIn(DispatchFunction f) override { f(); } 116 | 117 | private: 118 | void onTimer() override { 119 | while (gtk_events_pending()) gtk_main_iteration(); 120 | } 121 | 122 | void MakeWebView(bool debug) { 123 | webview_ = webkit_web_view_new(); 124 | WebKitUserContentManager *manager = 125 | webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(webview_)); 126 | 127 | g_signal_connect(manager, "script-message-received::external", 128 | G_CALLBACK(+[](WebKitUserContentManager *, 129 | WebKitJavascriptResult *r, gpointer arg) { 130 | auto *w = static_cast(arg); 131 | JSCValue *value = 132 | webkit_javascript_result_get_js_value(r); 133 | char *s = jsc_value_to_string(value); 134 | w->OnBrowserMessage(s); 135 | g_free(s); 136 | }), 137 | this); 138 | webkit_user_content_manager_register_script_message_handler(manager, 139 | "external"); 140 | 141 | WebKitSettings *settings = webkit_settings_new(); 142 | 143 | webkit_settings_set_allow_file_access_from_file_urls(settings, true); 144 | webkit_settings_set_allow_universal_access_from_file_urls(settings, true); 145 | webkit_settings_set_javascript_can_access_clipboard(settings, true); 146 | webkit_settings_set_allow_modal_dialogs(settings, true); 147 | 148 | if (debug) { 149 | webkit_settings_set_enable_write_console_messages_to_stdout(settings, 150 | true); 151 | webkit_settings_set_enable_developer_extras(settings, true); 152 | } 153 | webkit_web_view_set_settings(WEBKIT_WEB_VIEW(webview_), settings); 154 | } 155 | 156 | Steinberg::FUnknownPtr run_loop_; 157 | 158 | GtkWidget *window_; 159 | GtkWidget *webview_; 160 | }; 161 | 162 | // static 163 | std::unique_ptr MakeWebview(bool debug, 164 | Steinberg::IPlugFrame *plug_frame, 165 | void *window, 166 | WebviewCreatedCallback created_cb) { 167 | auto x11Parent = reinterpret_cast(window); 168 | 169 | auto webview = std::make_unique(debug, plug_frame, 170 | x11Parent, created_cb); 171 | return std::move(webview); 172 | } 173 | 174 | } // namespace vstwebview -------------------------------------------------------------------------------- /src/vstwebview/osx/webview_osx.mm: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Noah Schairer 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include "base/source/fobject.h" 21 | #include "vstwebview/webview.h" 22 | 23 | // Largely a port of https://github.com/webview/webview 24 | // Extended to run in an existing NSApplication 25 | 26 | // OSX Imports 27 | #include 28 | #include 29 | 30 | #define NSBackingStoreBuffered 2 31 | #define NSWindowStyleMaskResizable 8 32 | #define NSWindowStyleMaskMiniaturizable 4 33 | #define NSWindowStyleMaskTitled 1 34 | #define NSWindowStyleMaskClosable 2 35 | #define NSApplicationActivationPolicyRegular 0 36 | #define WKUserScriptInjectionTimeAtDocumentStart 0 37 | 38 | id operator"" _cls(const char *s, std::size_t) { return (id)objc_getClass(s); } 39 | SEL operator"" _sel(const char *s, std::size_t) { return sel_registerName(s); } 40 | id operator"" _str(const char *s, std::size_t) { 41 | return ((id(*)(id, SEL, const char *))objc_msgSend)("NSString"_cls, "stringWithUTF8String:"_sel, 42 | s); 43 | } 44 | 45 | namespace vstwebview { 46 | class WebviewOSX : public vstwebview::Webview { 47 | public: 48 | WebviewOSX(bool debug, Steinberg::IPlugFrame *plug_frame, id parentView, 49 | WebviewCreatedCallback created) { 50 | Class cls = objc_getClass("WebviewDelegate"); 51 | if (cls == NULL) { 52 | cls = objc_allocateClassPair((Class) "NSResponder"_cls, "WebviewDelegate", 0); 53 | class_addMethod(cls, "userContentController:didReceiveScriptMessage:"_sel, 54 | (IMP)(+[](id self, SEL, id, id msg) { 55 | auto w = (WebviewOSX *)objc_getAssociatedObject(self, "webview"); 56 | assert(w); 57 | w->OnBrowserMessage(((const char *(*)(id, SEL))objc_msgSend)( 58 | ((id(*)(id, SEL))objc_msgSend)(msg, "body"_sel), "UTF8String"_sel)); 59 | }), 60 | "v@:@@"); 61 | objc_registerClassPair(cls); 62 | } 63 | 64 | auto delegate = ((id(*)(id, SEL))objc_msgSend)((id)cls, "new"_sel); 65 | objc_setAssociatedObject(delegate, "webview", (id)this, OBJC_ASSOCIATION_ASSIGN); 66 | 67 | // Webview config 68 | auto config = ((id(*)(id, SEL))objc_msgSend)("WKWebViewConfiguration"_cls, "new"_sel); 69 | 70 | m_manager = ((id(*)(id, SEL))objc_msgSend)(config, "userContentController"_sel); 71 | 72 | ((void (*)(id, SEL, id, id))objc_msgSend)(m_manager, "addScriptMessageHandler:name:"_sel, 73 | delegate, "external"_str); 74 | 75 | if (debug) { 76 | ((id(*)(id, SEL, id, id))objc_msgSend)( 77 | ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel), "setValue:forKey:"_sel, 78 | ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls, "numberWithBool:"_sel, 1), 79 | "developerExtrasEnabled"_str); 80 | } 81 | // [[config preferences] setValue:@YES forKey:@"javaScriptCanAccessClipboard"]; 82 | ((id(*)(id, SEL, id, id))objc_msgSend)( 83 | ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel), "setValue:forKey:"_sel, 84 | ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls, "numberWithBool:"_sel, 1), 85 | "javaScriptCanAccessClipboard"_str); 86 | 87 | // [[config preferences] setValue:@YES forKey:@"DOMPasteAllowed"]; 88 | ((id(*)(id, SEL, id, id))objc_msgSend)( 89 | ((id(*)(id, SEL))objc_msgSend)(config, "preferences"_sel), "setValue:forKey:"_sel, 90 | ((id(*)(id, SEL, BOOL))objc_msgSend)("NSNumber"_cls, "numberWithBool:"_sel, 1), 91 | "DOMPasteAllowed"_str); 92 | 93 | // Create webview 94 | webview_ = ((id(*)(id, SEL))objc_msgSend)("WKWebView"_cls, "alloc"_sel); 95 | 96 | OnDocumentCreate(R"script( 97 | window.external = { 98 | invoke: function(s) { 99 | window.webkit.messageHandlers.external.postMessage(s); 100 | }, 101 | }; 102 | )script"); 103 | 104 | ((void (*)(id, SEL, CGRect, id))objc_msgSend)(webview_, "initWithFrame:configuration:"_sel, 105 | CGRectMake(0, 0, 100, 100), config); 106 | 107 | ((void (*)(id, SEL, id))objc_msgSend)(parentView, "addSubview:"_sel, webview_); 108 | 109 | window_ = parentView; 110 | 111 | created(this); 112 | } 113 | static char *plugin_path(void) { 114 | Dl_info info; 115 | if (dladdr((const char *)plugin_path, &info) != 0) { 116 | return strdup(info.dli_fname); 117 | } else { 118 | return nullptr; 119 | } 120 | } 121 | std::string ContentRootURI() const override { 122 | std::string path = plugin_path(); 123 | std::string resources = "Resources"; 124 | 125 | CFURLRef url = CFURLCreateWithString( 126 | NULL, CFStringCreateWithCString(NULL, path.c_str(), kCFStringEncodingUTF8), NULL); 127 | url = CFURLCreateCopyDeletingLastPathComponent(NULL, url); 128 | url = CFURLCreateCopyDeletingLastPathComponent(NULL, url); 129 | url = CFURLCreateCopyAppendingPathComponent( 130 | NULL, url, CFStringCreateWithCString(NULL, resources.c_str(), kCFStringEncodingUTF8), 131 | false); 132 | 133 | auto utf16length = CFStringGetLength(CFURLGetString(url)); 134 | CFIndex maxUtf8len = CFStringGetMaximumSizeForEncoding(utf16length, kCFStringEncodingUTF8); 135 | 136 | char buffer[maxUtf8len]; 137 | CFStringGetCString(CFURLGetString(url), buffer, maxUtf8len, kCFStringEncodingUTF8); 138 | 139 | // XXX relative path maybe? 140 | return "file://" + std::string(buffer); 141 | } 142 | void OnDocumentCreate(const std::string &js) override { 143 | ((void (*)(id, SEL, id))objc_msgSend)( 144 | m_manager, "addUserScript:"_sel, 145 | ((id(*)(id, SEL, id, long, BOOL))objc_msgSend)( 146 | ((id(*)(id, SEL))objc_msgSend)("WKUserScript"_cls, "alloc"_sel), 147 | "initWithSource:injectionTime:forMainFrameOnly:"_sel, 148 | ((id(*)(id, SEL, const char *))objc_msgSend)("NSString"_cls, 149 | "stringWithUTF8String:"_sel, js.c_str()), 150 | WKUserScriptInjectionTimeAtDocumentStart, 1)); 151 | } 152 | void SetTitle(const std::string &title) override { 153 | // XXX TODO - I don't think this is necessary since not creating a new window 154 | } 155 | void SetViewSize(int width, int height, SizeHint hints) override { 156 | // XXX TODO - make use of size hints -- again might not be necessary since its a subview.. 157 | CGSize size = CGSizeMake(width, height); 158 | ((id(*)(id, SEL, CGSize))objc_msgSend)(webview_, "setFrameSize:"_sel, size); 159 | } 160 | void Navigate(const std::string &url) override { 161 | auto nsurl = ((id(*)(id, SEL, id))objc_msgSend)( 162 | "NSURL"_cls, "URLWithString:"_sel, 163 | ((id(*)(id, SEL, const char *))objc_msgSend)("NSString"_cls, "stringWithUTF8String:"_sel, 164 | url.c_str())); 165 | 166 | ((void (*)(id, SEL, id))objc_msgSend)( 167 | webview_, "loadRequest:"_sel, 168 | ((id(*)(id, SEL, id))objc_msgSend)("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl)); 169 | } 170 | void *PlatformWindow() const override { return window_; }; 171 | void Terminate() override { 172 | // XXX TODO - Might not be necessary since a sub view not an actual window 173 | } 174 | void EvalJS(const std::string &js, ResultCallback rs) override { 175 | auto foo = ^(id ret, id err) { 176 | if (ret == NULL) { 177 | /* Could be useful! 178 | char * errorText = 179 | ((char*(*)(id, SEL))objc_msgSend)( 180 | ((id(*)(id, SEL))objc_msgSend)(err, "localizedDescription"_sel), 181 | "UTF8String"_sel); 182 | */ 183 | return; 184 | } else { 185 | //XXX TODO - Parse JSON correctly based on Obj-C type, using NSJSONSerialization 186 | //char *value = ((char *(*)(id, SEL))objc_msgSend)(ret, "UTF8String"_sel); 187 | //rs(value); 188 | } 189 | }; 190 | 191 | ((void (*)(id, SEL, id, dispatch_block_t))objc_msgSend)( 192 | webview_, "evaluateJavaScript:completionHandler:"_sel, 193 | ((id(*)(id, SEL, const char *))objc_msgSend)("NSString"_cls, "stringWithUTF8String:"_sel, 194 | js.c_str()), 195 | (dispatch_block_t)foo); 196 | } 197 | 198 | protected: 199 | void DispatchIn(vstwebview::DispatchFunction f) override { f(); } 200 | 201 | private: 202 | id window_; 203 | id webview_; 204 | id m_manager; 205 | }; 206 | 207 | std::unique_ptr MakeWebview(bool debug, Steinberg::IPlugFrame *plug_frame, void *window, 208 | WebviewCreatedCallback created_cb) { 209 | auto parentView = reinterpret_cast(window); 210 | auto webview = std::make_unique(debug, plug_frame, parentView, created_cb); 211 | return std::move(webview); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/vstwebview/webview.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Ryan Daum 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "vstwebview/webview.h" 16 | 17 | #include 18 | 19 | namespace vstwebview { 20 | 21 | void Webview::BindFunction(const std::string &name, 22 | Webview::FunctionBinding f) { 23 | auto js = "(function() { var name = '" + name + "';" + R"( 24 | var RPC = window._rpc = (window._rpc || {nextSeq: 1}); 25 | window[name] = function() { 26 | var seq = RPC.nextSeq++; 27 | var promise = new Promise(function(resolve, reject) { 28 | RPC[seq] = { 29 | resolve: resolve, 30 | reject: reject, 31 | }; 32 | }); 33 | window.external.invoke(JSON.stringify({ 34 | id: seq, 35 | method: name, 36 | params: Array.prototype.slice.call(arguments), 37 | })); 38 | return promise; 39 | } 40 | })())"; 41 | OnDocumentCreate(js); 42 | bindings_[name] = FunctionBinding(std::move(f)); 43 | } 44 | 45 | void Webview::UnbindFunction(const std::string &name) { 46 | if (bindings_.find(name) != bindings_.end()) { 47 | auto js = "delete window['" + name + "'];"; 48 | OnDocumentCreate(js); 49 | EvalJS(js, [](const nlohmann::json &j) {}); 50 | bindings_.erase(name); 51 | } 52 | } 53 | 54 | void Webview::ResolveFunctionDispatch(int seq, int status, 55 | const nlohmann::json &result) { 56 | DispatchIn([this, status, seq, result]() { 57 | if (status == 0) { 58 | EvalJS("window._rpc[" + std::to_string(seq) + "].resolve(" + 59 | result.dump() + "); delete window._rpc[" + 60 | std::to_string(seq) + "]", 61 | [](const nlohmann::json &j) {}); 62 | } else { 63 | EvalJS("window._rpc[" + std::to_string(seq) + "].reject(" + 64 | result.dump() + "); delete window._rpc[" + 65 | std::to_string(seq) + "]", 66 | [](const nlohmann::json &j) {}); 67 | } 68 | }); 69 | } 70 | 71 | void Webview::OnBrowserMessage(const std::string &msg) { 72 | nlohmann::json msg_parsed = nlohmann::json::parse(msg); 73 | int seq = msg_parsed["id"]; 74 | std::string name = msg_parsed["method"]; 75 | nlohmann::json args = msg_parsed["params"]; 76 | const auto &it = bindings_.find(name); 77 | if (it == bindings_.end()) { 78 | return; 79 | } 80 | auto result = it->second(this, seq, name, args); 81 | ResolveFunctionDispatch(seq, 0, result); 82 | } 83 | 84 | } // namespace vstwebview -------------------------------------------------------------------------------- /src/vstwebview/webview_controller_bindings.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Ryan Daum 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "vstwebview/webview_controller_bindings.h" 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #include "pluginterfaces/base/ustring.h" 23 | 24 | namespace vstwebview { 25 | 26 | namespace { 27 | 28 | json SerializeParameter(Steinberg::Vst::Parameter *param) { 29 | auto &info = param->getInfo(); 30 | json j = { 31 | {"normalized", param->getNormalized()}, 32 | {"precision", param->getUnitID()}, 33 | {"unitID", param->getUnitID()}, 34 | {"info", 35 | { 36 | {"id", info.id}, 37 | {"title", VST3::StringConvert::convert(info.title)}, 38 | {"stepCount", info.stepCount}, 39 | {"flags", info.flags}, 40 | {"defaultNormalizedValue", info.defaultNormalizedValue}, 41 | {"units", info.units}, 42 | {"shortTitle", VST3::StringConvert::convert(info.shortTitle)}, 43 | }}, 44 | }; 45 | bool isRangeParameter = 46 | (param->isA(Steinberg::Vst::RangeParameter::getFClassID())); 47 | j["isRangeParameter"] = isRangeParameter; 48 | if (isRangeParameter) { 49 | auto *range_param = dynamic_cast(param); 50 | j["min"] = range_param->getMin(); 51 | j["max"] = range_param->getMax(); 52 | } 53 | return j; 54 | } 55 | 56 | // Proxy IDependent through the webview for parameter object changes. 57 | class ParameterDependenciesProxy : public Steinberg::FObject { 58 | public: 59 | explicit ParameterDependenciesProxy(vstwebview::Webview *webview) 60 | : webview_(webview) {} 61 | 62 | void update(FUnknown *changedUnknown, Steinberg::int32 message) override { 63 | if (!webview_ || message != IDependent::kChanged) return; 64 | 65 | Steinberg::Vst::Parameter *changed_param; 66 | auto query_result = changedUnknown->queryInterface( 67 | Steinberg::Vst::RangeParameter::iid, (void **)&changed_param); 68 | 69 | if (query_result != Steinberg::kResultOk) return; 70 | 71 | webview_->EvalJS("notifyParameterChange(" + 72 | SerializeParameter(changed_param).dump() + ");", 73 | [](const json &r) {}); 74 | } 75 | 76 | private: 77 | vstwebview::Webview *webview_; 78 | }; 79 | 80 | } // namespace 81 | 82 | WebviewControllerBindings::WebviewControllerBindings( 83 | Steinberg::Vst::EditControllerEx1 *controller) 84 | : thread_checker_(Steinberg::Vst::ThreadChecker::create()), 85 | controller_(controller) { 86 | DeclareJSBinding( 87 | "getParameterObject", 88 | BindCallback(&WebviewControllerBindings::GetParameterObject)); 89 | DeclareJSBinding( 90 | "getParameterObjects", 91 | BindCallback(&WebviewControllerBindings::GetParameterObjects)); 92 | DeclareJSBinding( 93 | "subscribeParameter", 94 | BindCallback(&WebviewControllerBindings::SubscribeParameter)); 95 | DeclareJSBinding( 96 | "setParamNormalized", 97 | BindCallback(&WebviewControllerBindings::SetParameterNormalized)); 98 | DeclareJSBinding( 99 | "normalizedParamToPlain", 100 | BindCallback(&WebviewControllerBindings::NormalizedParamToPlain)); 101 | DeclareJSBinding( 102 | "getParamNormalized", 103 | BindCallback(&WebviewControllerBindings::GetParamNormalized)); 104 | DeclareJSBinding("beginEdit", 105 | BindCallback(&WebviewControllerBindings::BeginEdit)); 106 | DeclareJSBinding("performEdit", 107 | BindCallback(&WebviewControllerBindings::PerformEdit)); 108 | DeclareJSBinding("endEdit", 109 | BindCallback(&WebviewControllerBindings::EndEdit)); 110 | DeclareJSBinding("getParameterCount", 111 | BindCallback(&WebviewControllerBindings::GetParameterCount)); 112 | DeclareJSBinding("getSelectedUnit", 113 | BindCallback(&WebviewControllerBindings::GetSelectedUnit)); 114 | DeclareJSBinding("selectUnit", 115 | BindCallback(&WebviewControllerBindings::SelectUnit)); 116 | DeclareJSBinding("sendMessage", 117 | BindCallback(&WebviewControllerBindings::DoSendMessage)); 118 | } 119 | 120 | void WebviewControllerBindings::Bind(vstwebview::Webview *webview) { 121 | for (auto &binding : bindings_) { 122 | webview->BindFunction(binding.first, binding.second); 123 | } 124 | } 125 | 126 | vstwebview::Webview::FunctionBinding WebviewControllerBindings::BindCallback( 127 | CallbackFn fn) { 128 | return std::bind(fn, this, std::placeholders::_1, std::placeholders::_4); 129 | } 130 | 131 | // TODO parameter validation on all these JSON argument retrievals. Or crash. 132 | 133 | json WebviewControllerBindings::GetParameterObject(vstwebview::Webview *webview, 134 | const json &in) { 135 | thread_checker_->test(); 136 | int id = in[0]; 137 | auto *param = controller_->getParameterObject(id); 138 | if (!param) return json(); 139 | return SerializeParameter(param); 140 | } 141 | 142 | json WebviewControllerBindings::GetParameterObjects( 143 | vstwebview::Webview *webview, const json &in) { 144 | thread_checker_->test(); 145 | json out; 146 | for (int id : in[0]) { 147 | auto *param = controller_->getParameterObject(id); 148 | if (param) { 149 | out[id] = SerializeParameter(param); 150 | } 151 | } 152 | return out; 153 | } 154 | 155 | json WebviewControllerBindings::SetParameterNormalized( 156 | vstwebview::Webview *webview, const json &in) { 157 | thread_checker_->test(); 158 | int tag = in[0]; 159 | double value = in[1]; 160 | json out = 161 | controller_->setParamNormalized(tag, value) == Steinberg::kResultOk; 162 | return out; 163 | } 164 | 165 | json WebviewControllerBindings::NormalizedParamToPlain( 166 | vstwebview::Webview *webview, const json &in) { 167 | thread_checker_->test(); 168 | int tag = in[0]; 169 | double value = in[1]; 170 | json out = controller_->normalizedParamToPlain(tag, value); 171 | return out; 172 | } 173 | 174 | json WebviewControllerBindings::GetParamNormalized(vstwebview::Webview *webview, 175 | const json &in) { 176 | thread_checker_->test(); 177 | int tag = in[0]; 178 | json out = controller_->getParamNormalized(tag); 179 | return out; 180 | } 181 | 182 | json WebviewControllerBindings::BeginEdit(vstwebview::Webview *webview, 183 | const json &in) { 184 | thread_checker_->test(); 185 | int tag = in[0]; 186 | json out = controller_->beginEdit(tag) == Steinberg::kResultOk; 187 | return out; 188 | } 189 | 190 | json WebviewControllerBindings::PerformEdit(vstwebview::Webview *webview, 191 | const json &in) { 192 | thread_checker_->test(); 193 | int tag = in[0]; 194 | double value = in[1]; 195 | json out = controller_->performEdit(tag, value) == Steinberg::kResultOk; 196 | return out; 197 | } 198 | 199 | json WebviewControllerBindings::EndEdit(vstwebview::Webview *webview, 200 | const json &in) { 201 | thread_checker_->test(); 202 | int tag = in[0]; 203 | json out = controller_->endEdit(tag) == Steinberg::kResultOk; 204 | return out; 205 | } 206 | 207 | json WebviewControllerBindings::GetParameterCount(vstwebview::Webview *webview, 208 | const json &in) { 209 | thread_checker_->test(); 210 | json out = controller_->getParameterCount(); 211 | return out; 212 | } 213 | 214 | json WebviewControllerBindings::GetSelectedUnit(vstwebview::Webview *webview, 215 | const json &in) { 216 | thread_checker_->test(); 217 | json out = controller_->getSelectedUnit(); 218 | return out; 219 | } 220 | 221 | json WebviewControllerBindings::SelectUnit(vstwebview::Webview *webview, 222 | const json &in) { 223 | thread_checker_->test(); 224 | json out = controller_->selectUnit(in[0]); 225 | return out; 226 | } 227 | 228 | json WebviewControllerBindings::SubscribeParameter(vstwebview::Webview *webview, 229 | const json &in) { 230 | thread_checker_->test(); 231 | json out = controller_->getParameterCount(); 232 | int tag = in[0]; 233 | auto *param = controller_->getParameterObject(tag); 234 | if (!param) return json(); 235 | if (!param_dep_proxy_) { 236 | param_dep_proxy_ = std::make_unique(webview); 237 | } 238 | param->addDependent(param_dep_proxy_.get()); 239 | return true; 240 | } 241 | 242 | json WebviewControllerBindings::DoSendMessage(vstwebview::Webview *webview, 243 | const json &in) { 244 | thread_checker_->test(); 245 | std::string messageId = in[0]; 246 | json attributes = in[1]; 247 | if (auto msg = owned(controller_->allocateMessage())) { 248 | msg->setMessageID(messageId.c_str()); 249 | auto msg_attrs = msg->getAttributes(); 250 | for (const auto &k : attributes.items()) { 251 | auto type = k.value().type(); 252 | auto attr_id = k.key().c_str(); 253 | if (type == json::value_t::number_float) { 254 | msg_attrs->setFloat(attr_id, k.value()); 255 | } else if (type == json::value_t::number_integer || 256 | type == json::value_t::number_unsigned || 257 | type == json::value_t::boolean) { 258 | msg_attrs->setInt(attr_id, k.value()); 259 | } else if (type == json::value_t::string) { 260 | std::string str = k.value(); 261 | Steinberg::Vst::TChar t_char_str[128]; 262 | VST3::StringConvert::convert(str, t_char_str); 263 | msg_attrs->setString(attr_id, t_char_str); 264 | } else if (type == json::value_t::binary) { 265 | json::binary_t b = k.value(); 266 | msg_attrs->setBinary(attr_id, b.data(), b.size()); 267 | } 268 | } 269 | controller_->sendMessage(msg); 270 | } 271 | 272 | return true; 273 | } 274 | 275 | void WebviewControllerBindings::DeclareJSBinding( 276 | const std::string &name, vstwebview::Webview::FunctionBinding binding) { 277 | bindings_.push_back({name, binding}); 278 | } 279 | 280 | } // namespace vstwebview -------------------------------------------------------------------------------- /src/vstwebview/webview_message_listener.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Ryan Daum 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "vstwebview/webview_message_listener.h" 16 | 17 | #include 18 | 19 | #include "vstwebview/webview.h" 20 | 21 | namespace vstwebview { 22 | 23 | void WebviewMessageListener::Subscribe( 24 | const std::string &receiver, const std::string &message_id, 25 | const std::vector &attributes) { 26 | subscriptions_[message_id] = {message_id, attributes, receiver}; 27 | } 28 | 29 | json WebviewMessageListener::SerializeMessage( 30 | Steinberg::Vst::IMessage *message, 31 | const WebviewMessageListener::MessageDescriptor &descriptor) { 32 | auto attributes = message->getAttributes(); 33 | 34 | json j = {{"messageId", message->getMessageID()}}; 35 | for (const auto &attr : descriptor.attributes) { 36 | switch (attr.type) { 37 | case MessageAttribute::Type::INT: 38 | Steinberg::int64 i; 39 | if (attributes->getInt(attr.name.c_str(), i) == 40 | Steinberg::kResultTrue) { 41 | j[attr.name] = i; 42 | } 43 | break; 44 | case MessageAttribute::Type::FLOAT: 45 | double f; 46 | if (attributes->getFloat(attr.name.c_str(), f) == 47 | Steinberg::kResultTrue) { 48 | j[attr.name] = f; 49 | } 50 | break; 51 | case MessageAttribute::Type::STRING: 52 | Steinberg::Vst::TChar str[128]; 53 | if (attributes->getString(attr.name.c_str(), str, 54 | 128 * sizeof(Steinberg::Vst::TChar)) == 55 | Steinberg::kResultTrue) { 56 | j[attr.name] = str; 57 | } 58 | break; 59 | case MessageAttribute::Type::BINARY: 60 | const void *addr; 61 | Steinberg::uint32 size; 62 | if (attributes->getBinary(attr.name.c_str(), addr, size) == 63 | Steinberg::kResultTrue) { 64 | std::vector data(size / sizeof(double)); 65 | std::memcpy(data.data(), addr, size); 66 | j[attr.name] = data; 67 | } 68 | break; 69 | } 70 | } 71 | return j; 72 | } 73 | 74 | Steinberg::tresult WebviewMessageListener::Notify( 75 | Steinberg::Vst::IMessage *message) { 76 | auto *msg_id = message->getMessageID(); 77 | 78 | const auto &it = subscriptions_.find(msg_id); 79 | if (it == subscriptions_.end()) return Steinberg::kResultFalse; 80 | 81 | auto json = SerializeMessage(message, it->second.descriptor); 82 | auto send_message_js = it->second.notify_function + "(" + json.dump() + ");"; 83 | webview_->EvalJS(send_message_js, [](const nlohmann::json &res) {}); 84 | return Steinberg::kResultOk; 85 | } 86 | 87 | } // namespace vstwebview -------------------------------------------------------------------------------- /src/vstwebview/webview_pluginview.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Ryan Daum 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "vstwebview/webview_pluginview.h" 16 | 17 | #include "vstwebview/webview_controller_bindings.h" 18 | 19 | namespace vstwebview { 20 | 21 | WebviewPluginView::WebviewPluginView( 22 | Steinberg::Vst::EditController *controller, 23 | const std::string &title, 24 | const std::vector &bindings, 25 | Steinberg::ViewRect *size, 26 | const std::string &uri) 27 | : Steinberg::Vst::EditorView(controller, size), title_(title), 28 | bindings_(bindings), uri_(uri) {} 29 | 30 | Steinberg::tresult WebviewPluginView::isPlatformTypeSupported( 31 | Steinberg::FIDString type) { 32 | return Steinberg::kResultTrue; 33 | } 34 | 35 | Steinberg::tresult WebviewPluginView::onSize(Steinberg::ViewRect *newSize) { 36 | { 37 | std::lock_guard webview_lock(webview_mutex_); 38 | if (webview_handle_) { 39 | webview_handle_->SetViewSize(newSize->getWidth(), newSize->getHeight(), 40 | vstwebview::Webview::SizeHint::kNone); 41 | } 42 | } 43 | return Steinberg::Vst::EditorView::onSize(newSize); 44 | } 45 | 46 | void WebviewPluginView::attachedToParent() { 47 | if (!webview_handle_) { 48 | auto init_function = [this](vstwebview::Webview *webview) { 49 | for (auto binding : bindings_) { 50 | binding->Bind(webview); 51 | } 52 | webview->SetTitle(title_); 53 | webview->SetViewSize(rect.getWidth(), rect.getHeight(), 54 | vstwebview::Webview::SizeHint::kFixed); 55 | 56 | if (!uri_.empty()) { 57 | webview->Navigate(uri_); 58 | } else { 59 | webview->Navigate(webview->ContentRootURI() + "/index.html"); 60 | } 61 | }; 62 | 63 | webview_handle_ = 64 | vstwebview::MakeWebview(true, plugFrame, systemWindow, init_function); 65 | } 66 | 67 | EditorView::attachedToParent(); 68 | } 69 | 70 | Steinberg::tresult WebviewPluginView::setFrame(Steinberg::IPlugFrame *frame) { 71 | return CPluginView::setFrame(frame); 72 | } 73 | 74 | Steinberg::tresult WebviewPluginView::onFocus(Steinberg::TBool a_bool) { 75 | return Steinberg::kResultOk; 76 | } 77 | 78 | void WebviewPluginView::removedFromParent() { 79 | if (webview_handle_) { 80 | webview_handle_->Terminate(); 81 | } 82 | 83 | EditorView::removedFromParent(); 84 | } 85 | 86 | Steinberg::tresult WebviewPluginView::canResize() { 87 | return Steinberg::kResultFalse; 88 | } 89 | 90 | } // namespace vstwebview 91 | -------------------------------------------------------------------------------- /src/vstwebview/win32/webview_edge_chromium.cc: -------------------------------------------------------------------------------- 1 | #include "vstwebview/win32/webview_edge_chromium.h" 2 | 3 | #include 4 | namespace winrt::impl { 5 | template 6 | auto wait_for(Async const &async, Windows::Foundation::TimeSpan const &timeout); 7 | } 8 | #include 9 | #include 10 | 11 | using Microsoft::WRL::Callback; 12 | 13 | extern "C" { 14 | static int SignatureFunctionForDLL = 0; 15 | } 16 | 17 | namespace vstwebview { 18 | 19 | EdgeChromiumBrowser::EdgeChromiumBrowser(HWND parent_window, bool debug, 20 | WebviewCreatedCallback created_cb) 21 | : WebviewWin32(parent_window, debug, created_cb) { 22 | SetFilePaths(); 23 | controller_completed_handler_ = Microsoft::WRL::Callback< 24 | ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( 25 | this, &EdgeChromiumBrowser::OnControllerCreated); 26 | environment_completed_handler_ = 27 | Callback( 28 | this, &EdgeChromiumBrowser::OnEnvironmentCreated); 29 | message_received_handler_ = 30 | Callback( 31 | this, &EdgeChromiumBrowser::OnWebMessageReceived); 32 | permission_requested_handler_ = 33 | Callback( 34 | this, &EdgeChromiumBrowser::OnPermissionRequested); 35 | } 36 | 37 | EdgeChromiumBrowser::~EdgeChromiumBrowser() { 38 | wv2_controller_->Release(); 39 | webview2_->Release(); 40 | settings_->Release(); 41 | } 42 | 43 | HRESULT EdgeChromiumBrowser::OnWebMessageReceived( 44 | ICoreWebView2 *sender, ICoreWebView2WebMessageReceivedEventArgs *args) { 45 | LPWSTR message; 46 | args->TryGetWebMessageAsString(&message); 47 | OnBrowserMessage(winrt::to_string(message)); 48 | sender->PostWebMessageAsString(message); 49 | 50 | CoTaskMemFree(message); 51 | return S_OK; 52 | } 53 | 54 | HRESULT EdgeChromiumBrowser::OnPermissionRequested( 55 | ICoreWebView2 *sender, ICoreWebView2PermissionRequestedEventArgs *args) { 56 | COREWEBVIEW2_PERMISSION_KIND kind; 57 | args->get_PermissionKind(&kind); 58 | if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { 59 | args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); 60 | } 61 | return S_OK; 62 | } 63 | 64 | HRESULT EdgeChromiumBrowser::OnEnvironmentCreated( 65 | HRESULT result, ICoreWebView2Environment *environment) { 66 | if (!SUCCEEDED(result)) { 67 | return E_FAIL; 68 | } 69 | environment->CreateCoreWebView2Controller( 70 | window_, controller_completed_handler_.Get()); 71 | return S_OK; 72 | } 73 | 74 | HRESULT EdgeChromiumBrowser::OnControllerCreated( 75 | HRESULT result, ICoreWebView2Controller *controller) { 76 | if (!SUCCEEDED(result) || !controller) { 77 | return E_FAIL; 78 | } 79 | 80 | wv2_controller_ = controller; 81 | wv2_controller_->AddRef(); 82 | if (wv2_controller_->get_CoreWebView2(&webview2_) != S_OK) { 83 | return E_FAIL; 84 | } 85 | 86 | ICoreWebView2_10 *webview_2_10; 87 | if (webview2_->QueryInterface(IID_PPV_ARGS(&webview_2_10)) != S_OK) { 88 | return E_FAIL; 89 | } 90 | 91 | if (webview2_->get_Settings(&settings_) != S_OK) { 92 | return E_FAIL; 93 | } 94 | 95 | if (debug_) { 96 | if (webview2_->OpenDevToolsWindow() != S_OK) { 97 | return E_FAIL; 98 | } 99 | } 100 | 101 | ::EventRegistrationToken token; 102 | if (webview2_->add_WebMessageReceived(message_received_handler_.Get(), 103 | &token) != S_OK) { 104 | return E_FAIL; 105 | }; 106 | if (webview2_->add_PermissionRequested(permission_requested_handler_.Get(), 107 | &token) != S_OK) { 108 | return E_FAIL; 109 | } 110 | 111 | webview2_->AddRef(); 112 | 113 | OnDocumentCreate( 114 | "window.external={invoke:s=>window.chrome.webview." 115 | "postMessage(" 116 | "s)}"); 117 | created_cb_(this); 118 | return S_OK; 119 | } 120 | 121 | bool EdgeChromiumBrowser::Embed() { 122 | // Start callback chain here. 123 | HRESULT res = CreateCoreWebView2EnvironmentWithOptions( 124 | nullptr, user_data_path_, nullptr, environment_completed_handler_.Get()); 125 | if (!SUCCEEDED(res)) { 126 | 127 | return false; 128 | } 129 | 130 | return true; 131 | } 132 | 133 | void EdgeChromiumBrowser::Resize() { 134 | if (wv2_controller_ == nullptr) { 135 | return; 136 | } 137 | RECT bounds; 138 | GetClientRect(window_, &bounds); 139 | wv2_controller_->put_Bounds(bounds); 140 | } 141 | 142 | void EdgeChromiumBrowser::Navigate(const std::string &url) { 143 | auto wurl = winrt::to_hstring(url); 144 | webview2_->Navigate(wurl.c_str()); 145 | } 146 | 147 | void EdgeChromiumBrowser::OnDocumentCreate(const std::string &js) { 148 | auto wjs = winrt::to_hstring(js); 149 | webview2_->AddScriptToExecuteOnDocumentCreated(wjs.c_str(), nullptr); 150 | } 151 | 152 | void EdgeChromiumBrowser::EvalJS(const std::string &js, ResultCallback rs) { 153 | auto wjs = winrt::to_hstring(js); 154 | 155 | auto complete_handler = [rs](HRESULT errorCode, 156 | LPCWSTR resultObjectAsJson) -> HRESULT { 157 | if (!SUCCEEDED(errorCode)) { 158 | return E_FAIL; 159 | } 160 | std::wstring ws(resultObjectAsJson); 161 | std::string json_str(ws.begin(), ws.end()); 162 | auto j = nlohmann::json::parse(json_str); 163 | rs(j); 164 | }; 165 | 166 | webview2_->ExecuteScript( 167 | wjs.c_str(), 168 | Callback(complete_handler) 169 | .Get()); 170 | } 171 | 172 | bool EdgeChromiumBrowser::SetFilePaths() { 173 | if (!SUCCEEDED( 174 | SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, user_data_path_))) { 175 | return false; 176 | } 177 | 178 | return true; 179 | } 180 | 181 | void EdgeChromiumBrowser::DispatchIn(DispatchFunction f) { f(); } 182 | 183 | } // namespace vstwebview 184 | -------------------------------------------------------------------------------- /src/vstwebview/win32/webview_edge_chromium.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Ryan Daum 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "vstwebview/win32/webview_win32.h" 20 | 21 | #pragma comment(lib, "windowsapp") 22 | 23 | #include 24 | 25 | #include 26 | #pragma comment(lib, "shell32.lib") 27 | #pragma comment(lib, "ole32.lib") 28 | #pragma comment(lib, "oleaut32.lib") 29 | 30 | namespace vstwebview { 31 | 32 | // 33 | // Edge/Chromium browser engine 34 | // 35 | class EdgeChromiumBrowser : public WebviewWin32 { 36 | public: 37 | EdgeChromiumBrowser(HWND parent_window, bool debug, 38 | WebviewCreatedCallback created_cb); 39 | ~EdgeChromiumBrowser() override; 40 | 41 | bool Embed() override; 42 | 43 | void Navigate(const std::string &url) override; 44 | void OnDocumentCreate(const std::string &js) override; 45 | void EvalJS(const std::string &js, ResultCallback rs) override; 46 | void DispatchIn(DispatchFunction f) override; 47 | 48 | protected: 49 | void Resize() override; 50 | 51 | private: 52 | HRESULT OnControllerCreated(HRESULT result, 53 | ICoreWebView2Controller *controller); 54 | HRESULT OnEnvironmentCreated(HRESULT result, 55 | ICoreWebView2Environment *environment); 56 | HRESULT OnWebMessageReceived(ICoreWebView2 *sender, 57 | ICoreWebView2WebMessageReceivedEventArgs *args); 58 | HRESULT OnPermissionRequested( 59 | ICoreWebView2 *sender, ICoreWebView2PermissionRequestedEventArgs *args); 60 | bool SetFilePaths(); 61 | 62 | wchar_t user_data_path_[MAX_PATH]; 63 | 64 | ICoreWebView2 *webview2_ = nullptr; 65 | ICoreWebView2Controller *wv2_controller_ = nullptr; 66 | 67 | Microsoft::WRL::ComPtr< 68 | ICoreWebView2CreateCoreWebView2ControllerCompletedHandler> 69 | controller_completed_handler_; 70 | Microsoft::WRL::ComPtr< 71 | ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler> 72 | environment_completed_handler_; 73 | Microsoft::WRL::ComPtr 74 | message_received_handler_; 75 | Microsoft::WRL::ComPtr 76 | permission_requested_handler_; 77 | ICoreWebView2Settings *settings_; 78 | }; 79 | 80 | } // namespace vstwebview -------------------------------------------------------------------------------- /src/vstwebview/win32/webview_win32.cc: -------------------------------------------------------------------------------- 1 | #include "vstwebview/win32/webview_win32.h" 2 | 3 | #include "vstwebview/webview.h" 4 | #include "vstwebview/win32/webview_edge_chromium.h" 5 | 6 | #include 7 | namespace winrt::impl { 8 | template 9 | auto wait_for(Async const &async, Windows::Foundation::TimeSpan const &timeout); 10 | } 11 | #include 12 | #include 13 | 14 | namespace vstwebview { 15 | 16 | WebviewWin32::WebviewWin32(HWND parent_window, bool debug, 17 | WebviewCreatedCallback created_cb) 18 | : debug_(debug), created_cb_(created_cb) { 19 | HINSTANCE hInstance = GetModuleHandle(nullptr); 20 | HICON icon = (HICON)LoadImage(hInstance, IDI_APPLICATION, IMAGE_ICON, 21 | GetSystemMetrics(SM_CXSMICON), 22 | GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR); 23 | 24 | WNDCLASSEXW wc; 25 | ZeroMemory(&wc, sizeof(WNDCLASSEX)); 26 | wc.cbSize = sizeof(WNDCLASSEX); 27 | wc.hInstance = hInstance; 28 | wc.lpszClassName = L"webview"; 29 | wc.hIcon = icon; 30 | wc.hIconSm = icon; 31 | wc.lpfnWndProc = 32 | (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> LRESULT { 33 | auto *w = reinterpret_cast( 34 | GetWindowLongPtr(hwnd, GWLP_USERDATA)); 35 | switch (msg) { 36 | case WM_SIZE: 37 | w->Resize(); 38 | break; 39 | case WM_CLOSE: 40 | DestroyWindow(hwnd); 41 | break; 42 | case WM_DESTROY: 43 | w->Terminate(); 44 | break; 45 | case WM_GETMINMAXINFO: { 46 | auto lpmmi = (LPMINMAXINFO)lp; 47 | if (w == nullptr) { 48 | return 0; 49 | } 50 | if (w->maxsz_.x > 0 && w->maxsz_.y > 0) { 51 | lpmmi->ptMaxSize = w->maxsz_; 52 | lpmmi->ptMaxTrackSize = w->maxsz_; 53 | } 54 | if (w->minsz_.x > 0 && w->minsz_.y > 0) { 55 | lpmmi->ptMinTrackSize = w->minsz_; 56 | } 57 | } break; 58 | default: 59 | return DefWindowProcW(hwnd, msg, wp, lp); 60 | } 61 | return 0; 62 | }); 63 | RegisterClassExW(&wc); 64 | window_ = CreateWindowW(L"webview", L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 65 | CW_USEDEFAULT, 640, 480, nullptr, nullptr, 66 | GetModuleHandle(nullptr), nullptr); 67 | SetWindowLongPtr(window_, GWLP_USERDATA, (LONG_PTR)this); 68 | 69 | if (parent_window) { 70 | SetParent(window_, parent_window); 71 | SetWindowLong(window_, GWL_STYLE, WS_CHILD); 72 | SetWindowPos(window_, nullptr, 0, 0, 0, 0, SWP_NOSIZE | SWP_FRAMECHANGED); 73 | RedrawWindow(window_, nullptr, nullptr, RDW_INVALIDATE); 74 | } 75 | 76 | SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); 77 | ShowWindow(window_, SW_SHOW); 78 | UpdateWindow(window_); 79 | SetFocus(window_); 80 | 81 | Resize(); 82 | } 83 | 84 | void WebviewWin32::Terminate() {} 85 | 86 | void WebviewWin32::SetTitle(const std::string &title) { 87 | SetWindowTextW(window_, winrt::to_hstring(title).c_str()); 88 | } 89 | 90 | void WebviewWin32::SetViewSize(int width, int height, SizeHint hints) { 91 | auto style = GetWindowLong(window_, GWL_STYLE); 92 | if (hints == SizeHint::kFixed) { 93 | style &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX); 94 | } else { 95 | style |= (WS_THICKFRAME | WS_MAXIMIZEBOX); 96 | } 97 | SetWindowLong(window_, GWL_STYLE, style); 98 | 99 | if (hints == SizeHint::kMax) { 100 | maxsz_.x = width; 101 | maxsz_.y = height; 102 | } else if (hints == SizeHint::kMin) { 103 | minsz_.x = width; 104 | minsz_.y = height; 105 | } else { 106 | RECT r; 107 | r.left = r.top = 0; 108 | r.right = width; 109 | r.bottom = height; 110 | AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0); 111 | SetWindowPos(window_, NULL, r.left, r.top, r.right - r.left, 112 | r.bottom - r.top, 113 | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE | SWP_FRAMECHANGED); 114 | Resize(); 115 | } 116 | } 117 | 118 | std::string WebviewWin32::ContentRootURI() const { 119 | wchar_t dll[MAX_PATH]; 120 | HMODULE hm = NULL; 121 | if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 122 | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, 123 | L"SignatureFunctionForDLL", &hm) == 0) { 124 | return ""; 125 | } 126 | if (GetModuleFileNameW(hm, dll, sizeof(dll)) == 0) { 127 | return ""; 128 | } 129 | if (PathRemoveFileSpecW(dll) == 0) { 130 | return ""; 131 | } 132 | wchar_t path[MAX_PATH]; 133 | swprintf_s(path, MAX_PATH, L"%s\\..\\Resources\\", dll); 134 | 135 | wchar_t virtual_server_path[MAX_PATH]; 136 | 137 | GetFullPathNameW(path, MAX_PATH, virtual_server_path, nullptr); 138 | 139 | std::wstring_convert> converter; 140 | return converter.to_bytes(virtual_server_path); 141 | } 142 | 143 | // static 144 | std::unique_ptr MakeWebview(bool debug, 145 | Steinberg::IPlugFrame *plug_frame, 146 | void *window, 147 | WebviewCreatedCallback created_cb) { 148 | auto webview = 149 | std::make_unique((HWND)window, debug, created_cb); 150 | if (webview->Embed()) { 151 | return std::move(webview); 152 | } 153 | return nullptr; 154 | } 155 | 156 | } // namespace vstwebview -------------------------------------------------------------------------------- /src/vstwebview/win32/webview_win32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Ryan Daum 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "vstwebview/webview.h" 20 | 21 | #define WIN32_LEAN_AND_MEAN 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #pragma comment(lib, "user32.lib") 30 | #pragma comment(lib, "Shlwapi.lib") 31 | 32 | namespace vstwebview { 33 | 34 | // Abstract parent for both edge-chromium and edge variants. 35 | class WebviewWin32 : public Webview { 36 | public: 37 | WebviewWin32(HWND parent_window, bool debug, 38 | WebviewCreatedCallback created_cb); 39 | ~WebviewWin32() override = default; 40 | 41 | virtual bool Embed() = 0; 42 | 43 | void *PlatformWindow() const override { return (void *)window_; } 44 | void Terminate() override; 45 | void SetTitle(const std::string &title) override; 46 | void SetViewSize(int width, int height, SizeHint hints) override; 47 | 48 | std::string ContentRootURI() const override; 49 | 50 | protected: 51 | virtual void Resize(){}; 52 | 53 | protected: 54 | HWND window_; 55 | bool debug_; 56 | WebviewCreatedCallback created_cb_; 57 | 58 | private: 59 | POINT minsz_ = POINT{0, 0}; 60 | POINT maxsz_ = POINT{0, 0}; 61 | }; 62 | 63 | } // namespace vstwebview 64 | --------------------------------------------------------------------------------