├── .github └── workflows │ ├── compute_size_manual.yml │ ├── main.yml │ └── release.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── archive.txt ├── docs ├── Building.md ├── Comparison.md ├── Usage.md └── standard_gamepad.svg ├── examples ├── example_asyncify │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html ├── example_hi_dpi │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html ├── example_minimal │ ├── CMakeLists.txt │ ├── README.md │ └── main.cpp ├── example_offscreen_canvas │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html ├── example_pthread │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html ├── example_resizable_container │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html ├── example_resizable_container_with_handle │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html ├── example_resizable_full_window │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ └── shell.html └── index.html ├── external └── GLFW │ ├── glfw3.h │ └── glfw3native.h ├── include └── GLFW │ ├── emscripten_glfw3.h │ └── emscripten_glfw3_version.h ├── port ├── emscripten-glfw3.py └── glfw3.py ├── src ├── cpp │ ├── emscripten │ │ └── glfw3 │ │ │ ├── Clipboard.cpp │ │ │ ├── Clipboard.h │ │ │ ├── Config.h │ │ │ ├── Context.cpp │ │ │ ├── Context.h │ │ │ ├── Cursor.h │ │ │ ├── ErrorHandler.cpp │ │ │ ├── ErrorHandler.h │ │ │ ├── Events.cpp │ │ │ ├── Events.h │ │ │ ├── Joystick.cpp │ │ │ ├── Joystick.h │ │ │ ├── Keyboard.cpp │ │ │ ├── Keyboard.h │ │ │ ├── KeyboardMapping.h │ │ │ ├── Monitor.h │ │ │ ├── Mouse.h │ │ │ ├── Object.h │ │ │ ├── Types.h │ │ │ ├── Window.cpp │ │ │ └── Window.h │ └── glfw3.cpp └── js │ └── lib_emscripten_glfw3.js ├── test └── demo │ ├── CMakeLists.txt │ ├── CopyResource.cmake │ ├── shell.html │ └── src │ ├── Triangle.cpp │ ├── Triangle.h │ └── main.cpp ├── tools ├── create_keyboard_mapping.py └── create_local_port.py └── version.h.in /.github/workflows/compute_size_manual.yml: -------------------------------------------------------------------------------- 1 | name: Compute Size (Manual) 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | optimizationLevel: ['2', 'z'] 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | with: 17 | path: emscripten-glfw 18 | 19 | - name: Checkout emscripten 20 | uses: actions/checkout@v4 21 | with: 22 | repository: emscripten-core/emsdk 23 | path: emscripten 24 | 25 | - name: Install Emscripten 26 | working-directory: ${{github.workspace}}/emscripten 27 | run: | 28 | ./emsdk install latest 29 | ./emsdk activate latest 30 | source ./emsdk_env.sh 31 | emcc -v 32 | 33 | - name: Compiling with libglfw.js (-O${{ matrix.optimizationLevel }}) 34 | working-directory: ${{github.workspace}}/emscripten-glfw 35 | run: | 36 | source ${{github.workspace}}/emscripten/emsdk_env.sh 37 | rm -rf build 38 | mkdir build 39 | emcc --version 40 | emcc -sUSE_GLFW=3 examples/example_minimal/main.cpp --closure=1 -O${{ matrix.optimizationLevel }} -o build/index.html 41 | ls -l build 42 | js_size=$(stat --format="%s" build/index.js) 43 | wasm_size=$(stat --format="%s" build/index.wasm) 44 | total_size=$((js_size + wasm_size)) 45 | echo "js:$js_size, wasm:$wasm_size, total:$total_size" 46 | echo "TOTAL_SIZE_LIBRARY_GLFW=$total_size" >> $GITHUB_ENV 47 | 48 | - name: Compiling with contrib.glfw3 (-O${{ matrix.optimizationLevel }}) 49 | working-directory: ${{github.workspace}}/emscripten-glfw 50 | run: | 51 | source ${{github.workspace}}/emscripten/emsdk_env.sh 52 | rm -rf build 53 | mkdir build 54 | emcc --version 55 | emcc --use-port=contrib.glfw3:optimizationLevel=${{ matrix.optimizationLevel }} examples/example_minimal/main.cpp --closure=1 -O${{ matrix.optimizationLevel }} -o build/index.html 56 | ls -l build 57 | js_size=$(stat --format="%s" build/index.js) 58 | wasm_size=$(stat --format="%s" build/index.wasm) 59 | total_size=$((js_size + wasm_size)) 60 | delta=$(echo "scale=10; d=($total_size / $TOTAL_SIZE_LIBRARY_GLFW - 1) * 100; scale=2; d/1" | bc) 61 | echo "js:$js_size, wasm:$wasm_size, total:$total_size | ${delta}%" 62 | 63 | - name: Compiling with contrib.glfw3 (small) (-O${{ matrix.optimizationLevel }}) 64 | working-directory: ${{github.workspace}}/emscripten-glfw 65 | run: | 66 | source ${{github.workspace}}/emscripten/emsdk_env.sh 67 | rm -rf build 68 | mkdir build 69 | emcc --version 70 | emcc --use-port=contrib.glfw3:disableWarning=true:disableMultiWindow=true:disableJoystick=true:disableWebGL2=true:optimizationLevel=${{ matrix.optimizationLevel }} examples/example_minimal/main.cpp --closure=1 -O${{ matrix.optimizationLevel }} -o build/index.html 71 | ls -l build 72 | js_size=$(stat --format="%s" build/index.js) 73 | wasm_size=$(stat --format="%s" build/index.wasm) 74 | total_size=$((js_size + wasm_size)) 75 | delta=$(echo "scale=10; d=($total_size / $TOTAL_SIZE_LIBRARY_GLFW - 1) * 100; scale=2; d/1" | bc) 76 | echo "js:$js_size, wasm:$wasm_size, total:$total_size | ${delta}%" 77 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Emscripten Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | tags-ignore: 8 | - '*' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | with: 18 | path: emscripten-glfw 19 | 20 | - name: Checkout emscripten 21 | uses: actions/checkout@v4 22 | with: 23 | repository: emscripten-core/emsdk 24 | path: emscripten 25 | 26 | - name: Install Emscripten 27 | working-directory: ${{github.workspace}}/emscripten 28 | run: | 29 | ./emsdk install latest 30 | ./emsdk activate latest 31 | source ./emsdk_env.sh 32 | emcc -v 33 | 34 | - name: Setup prerequisites for CMake 35 | run: sudo apt-get install build-essential 36 | 37 | - name: Configure CMake Project 38 | working-directory: ${{github.workspace}}/emscripten-glfw 39 | run: | 40 | source ${{github.workspace}}/emscripten/emsdk_env.sh 41 | emcmake cmake -B build -DCMAKE_BUILD_TYPE=Release . 42 | 43 | - name: Compile all 44 | working-directory: ${{github.workspace}}/emscripten-glfw/build 45 | run: cmake --build . --target examples-all 46 | 47 | - name: Result 48 | working-directory: ${{github.workspace}}/emscripten-glfw/build 49 | run: ls -l examples/example_minimal 50 | 51 | - name: Configure CMake Project (small) 52 | working-directory: ${{github.workspace}}/emscripten-glfw 53 | run: | 54 | source ${{github.workspace}}/emscripten/emsdk_env.sh 55 | emcmake cmake -B build-small -DCMAKE_BUILD_TYPE=Release -DEMSCRIPTEN_GLFW3_DISABLE_JOYSTICK=ON -DEMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT=ON . 56 | 57 | - name: Compile all (small) 58 | working-directory: ${{github.workspace}}/emscripten-glfw/build-small 59 | run: cmake --build . --target examples-all 60 | 61 | - name: Result (small) 62 | working-directory: ${{github.workspace}}/emscripten-glfw/build-small 63 | run: ls -l examples/example_minimal 64 | 65 | - name: Compile with port 66 | working-directory: ${{github.workspace}}/emscripten-glfw 67 | run: | 68 | source ${{github.workspace}}/emscripten/emsdk_env.sh 69 | mkdir build-with-port 70 | emcc --version 71 | emcc --use-port=contrib.glfw3 examples/example_minimal/main.cpp --closure=1 -O2 -o build-with-port/index.html 72 | ls -l build-with-port 73 | 74 | - name: Compile with port (small) 75 | working-directory: ${{github.workspace}}/emscripten-glfw 76 | run: | 77 | source ${{github.workspace}}/emscripten/emsdk_env.sh 78 | mkdir build-with-port-small 79 | emcc --version 80 | emcc --use-port=contrib.glfw3:disableWarning=true:disableMultiWindow=true:disableJoystick=true:disableWebGL2=true examples/example_minimal/main.cpp --closure=1 -O2 -o build-with-port-small/index.html 81 | ls -l build-with-port-small 82 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # The purpose of this action is to test that the port defined in this project 2 | # (port/emscripten-glfw3.py) works properly and that the Release includes the 3 | # archive. 4 | # The output of the 2 "Compile" phases can be used as a sanity check on the sizes of the 5 | # minimal use case for each release. 6 | name: Release 7 | 8 | on: 9 | release: 10 | types: [published] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v4 19 | with: 20 | path: emscripten-glfw 21 | 22 | - name: Checkout emscripten 23 | uses: actions/checkout@v4 24 | with: 25 | repository: emscripten-core/emsdk 26 | path: emscripten 27 | 28 | - name: Install Emscripten 29 | working-directory: ${{github.workspace}}/emscripten 30 | run: | 31 | ./emsdk install latest 32 | ./emsdk activate latest 33 | source ./emsdk_env.sh 34 | emcc -v 35 | 36 | - name: Compile with released port 37 | working-directory: ${{github.workspace}}/emscripten-glfw 38 | run: | 39 | source ${{github.workspace}}/emscripten/emsdk_env.sh 40 | mkdir build-with-port 41 | emcc --version 42 | emcc --use-port=port/emscripten-glfw3.py examples/example_minimal/main.cpp --closure=1 -O2 -o build-with-port/index.html 43 | ls -l build-with-port 44 | 45 | - name: Compile with released port (small) 46 | working-directory: ${{github.workspace}}/emscripten-glfw 47 | run: | 48 | source ${{github.workspace}}/emscripten/emsdk_env.sh 49 | mkdir build-with-port-small 50 | emcc --version 51 | emcc --use-port=port/emscripten-glfw3.py:disableWarning=true:disableMultiWindow=true:disableJoystick=true:disableWebGL2=true examples/example_minimal/main.cpp --closure=1 -O2 -o build-with-port-small/index.html 52 | ls -l build-with-port-small 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | cmake-build-* 4 | .DS_Store 5 | __pycache__ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(emscripten-glfw_RELEASE_YEAR "2025") 4 | set(emscripten-glfw_RELEASE_MONTH "09" ) 5 | set(emscripten-glfw_RELEASE_DAY "27" ) 6 | 7 | set(emscripten-glfw_GLFW_VERSION "3.4.0") 8 | 9 | set(emscripten-glfw_PORT_VERSION "${emscripten-glfw_RELEASE_YEAR}${emscripten-glfw_RELEASE_MONTH}${emscripten-glfw_RELEASE_DAY}") 10 | set(emscripten-glfw_VERSION "${emscripten-glfw_GLFW_VERSION}.${emscripten-glfw_PORT_VERSION}") 11 | set(emscripten-glfw_VERSION_AS_NUMBER "3'4'0'${emscripten-glfw_PORT_VERSION}") 12 | set(emscripten-glfw_RELEASE_DATE "${emscripten-glfw_RELEASE_YEAR}-${emscripten-glfw_RELEASE_MONTH}-${emscripten-glfw_RELEASE_DAY}") 13 | 14 | project(emscripten-glfw VERSION "${emscripten-glfw_VERSION}" LANGUAGES CXX) 15 | 16 | if(NOT EMSCRIPTEN) 17 | message(FATAL_ERROR "This library must be compiled with emscripten") 18 | endif() 19 | 20 | set(CMAKE_CXX_STANDARD 17) 21 | 22 | # Use this CMake option to disable joystick support 23 | option(EMSCRIPTEN_GLFW3_DISABLE_JOYSTICK "Disable joystick support" OFF) 24 | 25 | # Use this CMake option to disable multi window support 26 | option(EMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT "Disable multi window support" OFF) 27 | 28 | set(glfw3_headers 29 | src/cpp/emscripten/glfw3/Clipboard.h 30 | src/cpp/emscripten/glfw3/Config.h 31 | src/cpp/emscripten/glfw3/Context.h 32 | src/cpp/emscripten/glfw3/Cursor.h 33 | src/cpp/emscripten/glfw3/ErrorHandler.h 34 | src/cpp/emscripten/glfw3/Events.h 35 | src/cpp/emscripten/glfw3/Keyboard.h 36 | src/cpp/emscripten/glfw3/KeyboardMapping.h 37 | src/cpp/emscripten/glfw3/Monitor.h 38 | src/cpp/emscripten/glfw3/Mouse.h 39 | src/cpp/emscripten/glfw3/Object.h 40 | src/cpp/emscripten/glfw3/Types.h 41 | src/cpp/emscripten/glfw3/Window.h 42 | ) 43 | 44 | set(glfw3_sources 45 | src/cpp/glfw3.cpp 46 | src/cpp/emscripten/glfw3/Clipboard.cpp 47 | src/cpp/emscripten/glfw3/Context.cpp 48 | src/cpp/emscripten/glfw3/ErrorHandler.cpp 49 | src/cpp/emscripten/glfw3/Events.cpp 50 | src/cpp/emscripten/glfw3/Keyboard.cpp 51 | src/cpp/emscripten/glfw3/Window.cpp 52 | ) 53 | 54 | if(NOT EMSCRIPTEN_GLFW3_DISABLE_JOYSTICK) 55 | list(APPEND glfw3_headers src/cpp/emscripten/glfw3/Joystick.h) 56 | list(APPEND glfw3_sources src/cpp/emscripten/glfw3/Joystick.cpp) 57 | endif() 58 | 59 | # Update the version appropriately 60 | configure_file("${CMAKE_CURRENT_LIST_DIR}/version.h.in" "${CMAKE_CURRENT_LIST_DIR}/include/GLFW/emscripten_glfw3_version.h" @ONLY) 61 | 62 | macro(add_glfw_lib LIBRARY_NAME) 63 | add_library(${LIBRARY_NAME} ${glfw3_sources}) 64 | target_sources(${LIBRARY_NAME} PUBLIC 65 | FILE_SET headers TYPE HEADERS BASE_DIRS src/cpp/emscripten/glfw3 FILES ${glfw3_headers} 66 | ) 67 | 68 | target_include_directories(${LIBRARY_NAME} PUBLIC external include) 69 | target_compile_definitions(${LIBRARY_NAME} PUBLIC 70 | $<$:EMSCRIPTEN_GLFW3_DISABLE_WARNING> 71 | $<$:EMSCRIPTEN_GLFW3_DISABLE_JOYSTICK> 72 | $<$:EMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT> 73 | ) 74 | target_link_options(${LIBRARY_NAME} PUBLIC 75 | "-lGL" 76 | "--js-library" "${CMAKE_CURRENT_LIST_DIR}/src/js/lib_emscripten_glfw3.js" 77 | "$<$:--closure=1>" 78 | ) 79 | endmacro() 80 | 81 | # main library 82 | add_glfw_lib("glfw3") 83 | 84 | # library compiled with -pthread 85 | add_glfw_lib("glfw3_pthread") 86 | target_compile_options(glfw3_pthread PUBLIC "-pthread") 87 | 88 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 89 | ### create archive/emscripten-glfw3-${emscripten-glfw_VERSION}.zip for the port 90 | set(ARCHIVE_DIR "${CMAKE_CURRENT_BINARY_DIR}/archive") 91 | set(ARCHIVE_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/archive-build") 92 | set(ARCHIVE_NAME "${ARCHIVE_DIR}/emscripten-glfw3-${emscripten-glfw_VERSION}.zip") 93 | 94 | # Read the list of files from archive.txt 95 | file(STRINGS "${CMAKE_CURRENT_LIST_DIR}/archive.txt" ARCHIVE_FILES) 96 | set(PROCESSED_FILES "") 97 | 98 | # Process each file 99 | foreach(FILE ${ARCHIVE_FILES}) 100 | set(ARCHIVE_FILE "${ARCHIVE_BUILD_DIR}/${FILE}") 101 | get_filename_component(FILE_DIR "${ARCHIVE_FILE}" DIRECTORY) 102 | file(MAKE_DIRECTORY "${FILE_DIR}") 103 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${FILE}" "${ARCHIVE_FILE}" @ONLY) 104 | list(APPEND PROCESSED_FILES "${ARCHIVE_FILE}") 105 | endforeach() 106 | 107 | add_custom_target("create-archive" 108 | WORKING_DIRECTORY "${ARCHIVE_BUILD_DIR}" 109 | COMMAND ${CMAKE_COMMAND} -E remove_directory ${ARCHIVE_DIR} 110 | COMMAND ${CMAKE_COMMAND} -E make_directory ${ARCHIVE_DIR} 111 | COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${ARCHIVE_NAME}" "--format=zip" "--mtime=${emscripten-glfw_RELEASE_DATE}" "--files-from=${CMAKE_CURRENT_LIST_DIR}/archive.txt" 112 | COMMAND ${CMAKE_COMMAND} -E sha512sum "${ARCHIVE_NAME}" > "${ARCHIVE_NAME}.sha512" 113 | COMMAND cat "${ARCHIVE_NAME}.sha512" 114 | DEPENDS ${PROCESSED_FILES} 115 | ) 116 | 117 | ### create a port pointing to archive 118 | add_custom_target("local-port" 119 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 120 | COMMAND tools/create_local_port.py "${CMAKE_CURRENT_BINARY_DIR}" "${emscripten-glfw_VERSION}" 121 | DEPENDS "create-archive" 122 | ) 123 | 124 | ### compile all the examples 125 | add_subdirectory("test/demo" EXCLUDE_FROM_ALL) 126 | add_subdirectory("examples/example_asyncify" EXCLUDE_FROM_ALL) 127 | add_subdirectory("examples/example_hi_dpi" EXCLUDE_FROM_ALL) 128 | add_subdirectory("examples/example_minimal" EXCLUDE_FROM_ALL) 129 | add_subdirectory("examples/example_offscreen_canvas" EXCLUDE_FROM_ALL) 130 | add_subdirectory("examples/example_pthread" EXCLUDE_FROM_ALL) 131 | add_subdirectory("examples/example_resizable_container" EXCLUDE_FROM_ALL) 132 | add_subdirectory("examples/example_resizable_container_with_handle" EXCLUDE_FROM_ALL) 133 | add_subdirectory("examples/example_resizable_full_window" EXCLUDE_FROM_ALL) 134 | add_custom_target("examples-all" 135 | COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/examples/index.html" "${CMAKE_CURRENT_BINARY_DIR}" 136 | DEPENDS 137 | "demo" 138 | "example-asyncify" 139 | "example-hi-dpi" 140 | "example-minimal" 141 | "example-offscreen-canvas" 142 | "example-pthread" 143 | "example-resizable-container" 144 | "example-resizable-container-with-handle" 145 | "example-resizable-full-window" 146 | ) 147 | 148 | ### create examples.zip for easy deploy 149 | set(EXAMPLES_BUILD_FILES "${CMAKE_CURRENT_BINARY_DIR}/examples_build_files.txt") 150 | set(EXAMPLES_ZIP_FILE "${CMAKE_CURRENT_BINARY_DIR}/examples.zip") 151 | add_custom_target("examples-zip" 152 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 153 | COMMAND find . -name main.html > "${EXAMPLES_BUILD_FILES}" 154 | COMMAND find . -name main.js >> "${EXAMPLES_BUILD_FILES}" 155 | COMMAND find . -name main.wasm >> "${EXAMPLES_BUILD_FILES}" 156 | COMMAND ${CMAKE_COMMAND} -E tar "cfv" "${EXAMPLES_ZIP_FILE}" "--format=zip" "--files-from=${EXAMPLES_BUILD_FILES}" 157 | DEPENDS "examples-all" 158 | ) 159 | endif() 160 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | lua-cmake license 204 | ================= 205 | MIT License 206 | 207 | Copyright (c) 2019 Lukas Böger 208 | 209 | Permission is hereby granted, free of charge, to any person obtaining a copy 210 | of this software and associated documentation files (the "Software"), to deal 211 | in the Software without restriction, including without limitation the rights 212 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 213 | copies of the Software, and to permit persons to whom the Software is 214 | furnished to do so, subject to the following conditions: 215 | 216 | The above copyright notice and this permission notice shall be included in all 217 | copies or substantial portions of the Software. 218 | 219 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 220 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 221 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 222 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 223 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 224 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 225 | SOFTWARE. 226 | 227 | lua license 228 | =========== 229 | Copyright © 1994–2021 Lua.org, PUC-Rio. 230 | 231 | Permission is hereby granted, free of charge, to any person obtaining a copy 232 | of this software and associated documentation files (the "Software"), to deal 233 | in the Software without restriction, including without limitation the rights 234 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 235 | copies of the Software, and to permit persons to whom the Software is 236 | furnished to do so, subject to the following conditions: 237 | 238 | The above copyright notice and this permission notice shall be included 239 | in all copies or substantial portions of the Software. 240 | 241 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 242 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 243 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 244 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 245 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 246 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 247 | DEALINGS IN THE SOFTWARE. 248 | -------------------------------------------------------------------------------- /archive.txt: -------------------------------------------------------------------------------- 1 | LICENSE.txt 2 | external/GLFW/glfw3.h 3 | include/GLFW/emscripten_glfw3.h 4 | include/GLFW/emscripten_glfw3_version.h 5 | port/glfw3.py 6 | src/cpp/emscripten/glfw3/Clipboard.h 7 | src/cpp/emscripten/glfw3/Clipboard.cpp 8 | src/cpp/emscripten/glfw3/Config.h 9 | src/cpp/emscripten/glfw3/Context.cpp 10 | src/cpp/emscripten/glfw3/Context.h 11 | src/cpp/emscripten/glfw3/Cursor.h 12 | src/cpp/emscripten/glfw3/ErrorHandler.cpp 13 | src/cpp/emscripten/glfw3/ErrorHandler.h 14 | src/cpp/emscripten/glfw3/Events.h 15 | src/cpp/emscripten/glfw3/Events.cpp 16 | src/cpp/emscripten/glfw3/Joystick.cpp 17 | src/cpp/emscripten/glfw3/Joystick.h 18 | src/cpp/emscripten/glfw3/Keyboard.cpp 19 | src/cpp/emscripten/glfw3/Keyboard.h 20 | src/cpp/emscripten/glfw3/KeyboardMapping.h 21 | src/cpp/emscripten/glfw3/Monitor.h 22 | src/cpp/emscripten/glfw3/Mouse.h 23 | src/cpp/emscripten/glfw3/Object.h 24 | src/cpp/emscripten/glfw3/Types.h 25 | src/cpp/emscripten/glfw3/Window.cpp 26 | src/cpp/emscripten/glfw3/Window.h 27 | src/cpp/glfw3.cpp 28 | src/js/lib_emscripten_glfw3.js -------------------------------------------------------------------------------- /docs/Building.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | This section describes how to build this project. 4 | 5 | > [!NOTE] 6 | > If you want to **use** this project, the recommended approach is to use the [Emscripten port](../README.md#quick-start-emscripten-port). 7 | > This section is only meant if you want to **work** on this project. 8 | 9 | ## Building 10 | 11 | The easiest way to build this project and run the examples, is to use `emcmake` and `emrun`. 12 | 13 | Assuming Emscripten is in your path: 14 | 15 | For `Debug` configuration: 16 | 17 | ```sh 18 | emcmake cmake -B build_debug -DCMAKE_BUILD_TYPE=Debug . 19 | cd build_debug 20 | cmake --build . --target examples-all 21 | emrun --browser firefox index.html 22 | ``` 23 | 24 | For `Release` configuration: 25 | 26 | ```sh 27 | emcmake cmake -B build_release -DCMAKE_BUILD_TYPE=Release . 28 | cd build_release 29 | cmake --build . --target examples-all 30 | emrun --browser firefox index.html 31 | ``` -------------------------------------------------------------------------------- /docs/Comparison.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Emscripten comes with a built-in implementation of GLFW (3) written in 100% JavaScript. 4 | This implementation is an alternate and more complete implementation of the GLFW API (currently 3.4.0). 5 | This document explains the differences. 6 | 7 | 8 | ## Brief History 9 | 10 | From the git history and the current source code, it is possible to gather the facts that the built-in GLFW 11 | implementation was implemented back in 2013 in pure JavaScript. 12 | It currently states that it implements the `3.2.1` version of the API released in 2016 13 | (as returned by `glfwGetVersion`). 14 | There have been 2 major releases since then (`3.3.0` released in 2019 and `3.4.0` released in 2024). 15 | It does not comply with the error handling paradigm of GLFW (`glfwSetErrorCallback` stores the function pointer but 16 | never uses it and some APIs throw an exception when called (like `glfwGetTimerFrequency`)). 17 | 18 | This library currently implements the latest GLFW API (3.4.0) in C++ (and JavaScript when necessary), 19 | using more modern browser technologies and addresses some issues from the JavaScript implementation. 20 | It tries to do its best to implement as much of the GLFW API that is possible to implement in the context 21 | of a web browser. When it can't, it handles the case gracefully and in compliance with error handling in GLFW. 22 | 23 | > [!NOTE] 24 | > This implementation has taken great care to be backward compatible with the built-in implementation, 25 | > for example, supporting `Module['canvas']`, so that it is a drop-in replacement. 26 | 27 | 28 | ## How to use? 29 | 30 | When compiling using the Emscripten compiler (`emcc` / `em++`), it is equally trivial to pick which flavor you want 31 | to use: 32 | 33 | * If you want to use the built-in implementation, you use the syntax `-sUSE_GLFW=3` 34 | * If you want to use this implementation, you use the syntax `--use-port=contrib.glfw3` 35 | 36 | > [!TIP] 37 | > When using `CMake`, you need to define it as both a compile and link option: 38 | > ```cmake 39 | > target_compile_options(${target} PUBLIC "--use-port=contrib.glfw3") 40 | > target_link_options(${target} PUBLIC "--use-port=contrib.glfw3") 41 | > ``` 42 | 43 | ## Main features comparison 44 | 45 | This section describes the main features implemented by both libraries. 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
Featureemscripten-glfw
(this implementation)
libglfw.js
(built-in implementation)
Single WindowYesYes
Multiple WindowsYesNo
MouseYes (includes sticky button behavior)Yes (NO sticky button behavior)
Touch (mobile)YesYes
KeyboardYes (includes sticky key behavior)Yes (NO sticky key behavior)
Keyboard / Meta KeyYes (implements workaround)Yes
JoystickYesYes
GamepadYesNo 3.3.x feature
FullscreenYesYes
Hi DPIYesYes
Clipboard (copy/paste)YesNo
Cursors (Standard + Custom)YesNo
Window Size constraints (size limits and aspect ratio)YesNo
Window OpacityYesNo
Window Status (Focused / Hovered / Position)YesNo
TimerYesYes (throws exception if called!)
Error HandingYesYes
Pthread / Offscreen canvas supportYesNo
148 | 149 | Please refer to the [exhaustive list](Usage.md#glfw-functions) of functions for further details. 150 | 151 | > [!NOTE] 152 | > This table is built with data from Emscripten 3.1.65 (2024/08/22) 153 | 154 | > [!WARNING] 155 | > Emscripten embeds `glfw3.h` version 3.3.8 yet implements version 3.2.1. 156 | -------------------------------------------------------------------------------- /examples/example_asyncify/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_asyncify LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-asyncify") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sASYNCIFY -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 15 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 16 | target_link_libraries(${target} PRIVATE glfw3) 17 | -------------------------------------------------------------------------------- /examples/example_asyncify/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate how to use asyncify which allows the code to be written like you 4 | would for a normal desktop application. 5 | 6 | ### Building instructions 7 | 8 | Using `emcc` from the command line (make sure it is in your path) 9 | 10 | ```sh 11 | mkdir build 12 | emcc --use-port=contrib.glfw3 -sASYNCIFY --shell-file=shell.html main.cpp -o build/index.html 13 | ``` 14 | 15 | Using `CMake`: 16 | 17 | ```cmake 18 | set(target "example-asyncify") 19 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-port=contrib.glfw3 -sASYNCIFY --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 20 | add_executable(${target} main.cpp) 21 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 22 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 23 | ``` 24 | 25 | ### Running instructions 26 | 27 | ```sh 28 | # start a webserver on port 8000 29 | python3 -m http.server -d build 30 | ``` 31 | 32 | Run in your browser: http://localhost:8000 -------------------------------------------------------------------------------- /examples/example_asyncify/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * The purpose of this example is to demonstrate how to use asyncify which allows the code to be written like you 26 | * would for a normal desktop application. */ 27 | 28 | //! Display error message in the Console 29 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 30 | { 31 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 32 | } 33 | 34 | //! jsRenderFrame: for the sake of this example, uses the canvas2D api to change the color of the screen / display a message 35 | EM_JS(void, jsRenderFrame, (GLFWwindow *glfwWindow, int w, int h, int fw, int fh, double mx, double my, int color, bool isFullscreen), { 36 | const ctx = Module['glfwGetCanvas'](glfwWindow).getContext('2d'); 37 | ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; 38 | ctx.fillRect(0, 0, fw, fh); // using framebuffer width/height 39 | ctx.save(); 40 | ctx.scale(fw/w, fh/h); 41 | const text = `${w}x${h} | ${mx}x${my} | CTRL+Q to terminate ${isFullscreen ? "" : '| CTRL+F for fullscreen'}`; 42 | ctx.font = '15px monospace'; 43 | ctx.fillStyle = `rgb(${255 - color}, 0, 0)`; 44 | ctx.fillText(text, 10 + color, 20 + color); 45 | ctx.restore(); 46 | }) 47 | 48 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen 49 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 50 | { 51 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 52 | switch(key) { 53 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 54 | case GLFW_KEY_F: emscripten_glfw_request_fullscreen(window, false, true); break; // ok from a keyboard event 55 | default: break; 56 | } 57 | } 58 | } 59 | 60 | //! computeSleepTime 61 | unsigned int computeSleepTime(unsigned int iFrameStart) 62 | { 63 | // this commented code represents the real math accounting for timer frequency 64 | // constexpr auto kFrameDurationInSeconds = 1.0f / 60.f; 65 | // auto frameEnd = glfwGetTimerValue(); 66 | // auto frameDurationInSeconds = static_cast(frameEnd - iFrameStart) / static_cast(glfwGetTimerFrequency()); 67 | // auto remaining = kFrameDurationInSeconds - frameDurationInSeconds; 68 | // if(remaining < 0) 69 | // remaining = 0; 70 | // return static_cast(remaining * 1000.f); 71 | 72 | // For the sake of this example 73 | return 16; // ~60fps as the work done is almost nothing 74 | } 75 | 76 | //! main 77 | int main() 78 | { 79 | // set a callback for errors otherwise if there is a problem, we won't know 80 | glfwSetErrorCallback(consoleErrorHandler); 81 | 82 | // print the version on the console 83 | printf("%s\n", glfwGetVersionString()); 84 | 85 | // initialize the library 86 | if(!glfwInit()) 87 | return -1; 88 | 89 | // no OpenGL (use canvas2D) 90 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 91 | 92 | // setting the association window <-> canvas 93 | emscripten_glfw_set_next_window_canvas_selector("#canvas"); 94 | 95 | // create the only window 96 | auto window = glfwCreateWindow(600, 400, "Asyncify | emscripten-glfw", nullptr, nullptr); 97 | if(!window) 98 | return -1; 99 | 100 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 101 | glfwSetKeyCallback(window, onKeyChange); 102 | 103 | int frameCount = 0; 104 | 105 | while(!glfwWindowShouldClose(window)) 106 | { 107 | auto frameStart = glfwGetTimerValue(); 108 | 109 | glfwPollEvents(); 110 | 111 | int w,h; 112 | glfwGetWindowSize(window, &w, &h); 113 | int fw,fh; 114 | glfwGetFramebufferSize(window, &fw, &fh); 115 | double mx,my; 116 | glfwGetCursorPos(window, &mx, &my); 117 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 118 | jsRenderFrame(window, w, h, fw, fh, mx, my, (int) color, emscripten_glfw_is_window_fullscreen(window)); 119 | 120 | // This is very important when using ASYNCIFY: you must sleep at least some otherwise it would be an 121 | // infinite loop and the UI would never refresh 122 | emscripten_sleep(computeSleepTime(frameStart)); 123 | } 124 | 125 | glfwTerminate(); 126 | } 127 | -------------------------------------------------------------------------------- /examples/example_asyncify/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | emscripten glfw 8 | 9 | 26 | 27 | 28 |

Welcome to Asyncify Example

29 | 30 |
31 |

Help

32 |
    33 |
  • CTRL+Q to terminate
  • 34 |
  • CTRL+F or click the button for fullscreen
  • 35 |
36 | 39 | {{{ SCRIPT }}} 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/example_hi_dpi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_hi_dpi LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-hi-dpi") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 15 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 16 | target_link_libraries(${target} PRIVATE glfw3) 17 | -------------------------------------------------------------------------------- /examples/example_hi_dpi/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate how to make the window Hi DPI aware. 4 | 5 | ### Building instructions 6 | 7 | Using `emcc` from the command line (make sure it is in your path) 8 | 9 | ```sh 10 | mkdir build 11 | emcc --use-port=contrib.glfw3 --shell-file=shell.html main.cpp -o build/index.html 12 | ``` 13 | 14 | Using `CMake`: 15 | 16 | ```cmake 17 | set(target "example-hi-dpi") 18 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-port=contrib.glfw3 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 19 | add_executable(${target} main.cpp) 20 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 21 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 22 | ``` 23 | 24 | ### Running instructions 25 | 26 | ```sh 27 | # start a webserver on port 8000 28 | python3 -m http.server -d build 29 | ``` 30 | 31 | Run in your browser: http://localhost:8000 32 | -------------------------------------------------------------------------------- /examples/example_hi_dpi/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * The purpose of this example is to demonstrate how to make the window Hi DPI aware. */ 26 | 27 | /** 28 | * !!!!!!!!!!!! IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT IMPORTANT !!!!!!!!!!!!!!!! 29 | * Important implementation detail: the canvas2D API offers a `scale` method and this example uses it to properly scale 30 | * the drawing based on the window content scale `glfwGetWindowContentScale`. Most other lower level APIs (like webgl) 31 | * do not offer such a "scaling" methodology and in general you should **always** associate the size returned by 32 | * `glfwGetFramebufferSize` to the viewport. For example: 33 | * 34 | * ```cpp 35 | * int width = 0, height = 0; 36 | * glfwGetFramebufferSize(fWindow, &width, &height); 37 | * glViewport(0, 0, width, height); 38 | * ``` 39 | */ 40 | 41 | //! Display error message in the Console 42 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 43 | { 44 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 45 | } 46 | 47 | //! jsRenderFrame: for the sake of this example, uses the canvas2D api to change the color of the screen / display a message 48 | EM_JS(void, jsRenderFrame, (GLFWwindow *glfwWindow, int w, int h, int sx, int sy, double mx, double my, int color, bool isFullscreen), { 49 | const ctx = Module['glfwGetCanvas'](glfwWindow).getContext('2d'); 50 | ctx.save(); 51 | ctx.scale(sx, sy); 52 | ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; 53 | ctx.fillRect(0, 0, w, h); 54 | ctx.font = '15px monospace'; 55 | ctx.fillStyle = `rgb(${255 - color}, 0, 0)`; 56 | ctx.fillText(`${w}x${h} | ${sx} | ${mx}x${my}`, 10 + color, 20 + color); 57 | ctx.fillText(`CTRL+H toggle HiDPI | CTRL+Q to terminate ${isFullscreen ? "" : '| CTRL+F for fullscreen'}`, 10 + color, 40 + color); 58 | ctx.restore(); 59 | }) 60 | 61 | //! Called for each frame 62 | void renderFrame(GLFWwindow *iWindow) 63 | { 64 | static int frameCount = 0; 65 | 66 | // poll events 67 | glfwPollEvents(); 68 | 69 | int w,h; glfwGetWindowSize(iWindow, &w, &h); 70 | float sx,sy; glfwGetWindowContentScale(iWindow, &sx, &sy); 71 | double mx,my; glfwGetCursorPos(iWindow, &mx, &my); 72 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 73 | jsRenderFrame(iWindow, w, h, sx, sy, mx, my, (int) color, emscripten_glfw_is_window_fullscreen(iWindow)); 74 | } 75 | 76 | //! The main loop (called by emscripten for each frame) 77 | void main_loop(void *iUserData) 78 | { 79 | if(auto window = reinterpret_cast(iUserData); !glfwWindowShouldClose(window)) 80 | { 81 | // not done => renderFrame 82 | renderFrame(window); 83 | } 84 | else 85 | { 86 | // done => terminating 87 | glfwTerminate(); 88 | emscripten_cancel_main_loop(); 89 | } 90 | } 91 | 92 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen, CTRL+H to toggle Hi DPI 93 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 94 | { 95 | static bool hiDPIAware = true; 96 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 97 | switch(key) { 98 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 99 | case GLFW_KEY_H: 100 | hiDPIAware = !hiDPIAware; 101 | glfwSetWindowAttrib(window, GLFW_SCALE_FRAMEBUFFER, hiDPIAware); 102 | glfwSetWindowTitle(window, hiDPIAware ? "Hi DPI Aware" : "NOT Hi Dpi Aware"); 103 | break; 104 | case GLFW_KEY_F: emscripten_glfw_request_fullscreen(window, false, true); break; // ok from a keyboard event 105 | default: break; 106 | } 107 | } 108 | } 109 | 110 | //! main 111 | int main() 112 | { 113 | // set a callback for errors otherwise if there is a problem, we won't know 114 | glfwSetErrorCallback(consoleErrorHandler); 115 | 116 | // print the version on the console 117 | printf("%s\n", glfwGetVersionString()); 118 | 119 | // initialize the library 120 | if(!glfwInit()) 121 | return -1; 122 | 123 | // no OpenGL (use canvas2D) 124 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 125 | 126 | // Make hi dpi aware (note that GLFW_SCALE_FRAMEBUFFER already defaults to GLFW_TRUE 127 | // so technically this is not necessary) 128 | glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); 129 | 130 | // setting the association window <-> canvas 131 | emscripten_glfw_set_next_window_canvas_selector("#canvas"); 132 | 133 | // create the only window 134 | auto window = glfwCreateWindow(320, 200, "Hi DPI Aware | emscripten-glfw", nullptr, nullptr); 135 | if(!window) 136 | return -1; 137 | 138 | // makes the canvas resizable and match the full window size 139 | emscripten_glfw_make_canvas_resizable(window, "window", nullptr); 140 | 141 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 142 | glfwSetKeyCallback(window, onKeyChange); 143 | 144 | // tell emscripten to use "main_loop" as the main loop (window is user data) 145 | emscripten_set_main_loop_arg(main_loop, window, 0, GLFW_FALSE); 146 | } 147 | -------------------------------------------------------------------------------- /examples/example_hi_dpi/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | emscripten glfw 8 | 9 | 26 | 27 | 28 | 29 |

Help

30 |
    31 |
  • CTRL+Q to terminate
  • 32 |
  • CTRL+H to toggle Hi DPI awareness
  • 33 |
  • CTRL+F or click the button for fullscreen
  • 34 |
35 | 38 | {{{ SCRIPT }}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/example_minimal/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_minimal LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-minimal") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1") 12 | 13 | add_executable(${target} main.cpp) 14 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 15 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 16 | target_link_libraries(${target} PRIVATE glfw3) 17 | -------------------------------------------------------------------------------- /examples/example_minimal/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to be as minimal as possible: initializes glfw, create window, then destroy it and terminate glfw. 4 | Uses the default shell that comes with emscripten. 5 | 6 | ### Building instructions 7 | 8 | Using `emcc` from the command line (make sure it is in your path) 9 | 10 | ```sh 11 | mkdir build 12 | emcc --use-port=contrib.glfw3 main.cpp -o build/index.html 13 | ``` 14 | 15 | Using `CMake`: 16 | 17 | ```cmake 18 | set(target "example-minimal") 19 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-port=contrib.glfw3") 20 | add_executable(${target} main.cpp) 21 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 22 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 23 | ``` 24 | 25 | ### Running instructions 26 | 27 | ```sh 28 | # start a webserver on port 8000 29 | python3 -m http.server -d build 30 | ``` 31 | 32 | Run in your browser: http://localhost:8000 -------------------------------------------------------------------------------- /examples/example_minimal/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | /** 24 | * The purpose of this example is to be as minimal as possible: initializes glfw, create window, then destroy it 25 | * and terminate glfw. Uses the default shell that comes with emscripten. */ 26 | 27 | int main() 28 | { 29 | // print the version on the console 30 | printf("%s\n", glfwGetVersionString()); 31 | printf("emscripten: v%d.%d.%d\n", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__); 32 | 33 | if(!glfwInit()) 34 | return -1; 35 | 36 | auto window = glfwCreateWindow(320, 200, "example_minimal | emscripten-glfw", nullptr, nullptr); 37 | glfwDestroyWindow(window); 38 | 39 | glfwTerminate(); 40 | } 41 | -------------------------------------------------------------------------------- /examples/example_offscreen_canvas/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_offscreen_canvas LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-offscreen-canvas") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread -sEXIT_RUNTIME=1 -sOFFSCREENCANVAS_SUPPORT -sOFFSCREENCANVASES_TO_PTHREAD='canvas' -sPROXY_TO_PTHREAD -s SAFE_HEAP=1 -s ASSERTIONS=1 -s WASM=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | target_compile_options(${target} PUBLIC "-pthread") 15 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 16 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 17 | target_link_libraries(${target} PRIVATE glfw3_pthread) 18 | -------------------------------------------------------------------------------- /examples/example_offscreen_canvas/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate the use of OffscreenCanvas which enables using WebGL contexts in pthreads 4 | 5 | ### Building instructions 6 | 7 | Using `emcc` from the command line (make sure it is in your path) 8 | 9 | ```sh 10 | mkdir build 11 | emcc --use-port=contrib.glfw3 -pthread -sPROXY_TO_PTHREAD -sOFFSCREENCANVAS_SUPPORT -sOFFSCREENCANVASES_TO_PTHREAD='canvas' -sMIN_WEBGL_VERSION=2 --shell-file=shell.html main.cpp -o build/index.html 12 | ``` 13 | 14 | ```cmake 15 | set(target "example-offscreen-canvas") 16 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread --use-port=contrib.glfw3 -sEXIT_RUNTIME=1 -sOFFSCREENCANVAS_SUPPORT -sOFFSCREENCANVASES_TO_PTHREAD='canvas' -sPROXY_TO_PTHREAD -s SAFE_HEAP=1 -s ASSERTIONS=1 -s WASM=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 17 | add_executable(${target} main.cpp) 18 | target_compile_options(${target} PUBLIC "-pthread") 19 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 20 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 21 | target_link_libraries(${target} PRIVATE glfw3_pthread) 22 | ``` 23 | 24 | ### Running instructions 25 | 26 | > [!NOTE] 27 | > Due to the [limitations](https://emscripten.org/docs/porting/pthreads.html) with browsers, this test needs to be run 28 | > with `emrun` which automatically takes care of the right headers. 29 | 30 | ```sh 31 | emrun --browser firefox build/main.html 32 | ``` -------------------------------------------------------------------------------- /examples/example_offscreen_canvas/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | /** 27 | * The purpose of this example is to demonstrate the use of OffscreenCanvas which enables using WebGL contexts in pthreads */ 28 | 29 | //! Display error message in the Console 30 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 31 | { 32 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 33 | } 34 | 35 | //! Called for each frame 36 | void renderFrame(GLFWwindow *iWindow) 37 | { 38 | static int frameCount = 0; 39 | 40 | // poll events 41 | glfwPollEvents(); 42 | 43 | // int w,h; glfwGetWindowSize(iWindow, &w, &h); 44 | // int fw,fh; glfwGetFramebufferSize(iWindow, &fw, &fh); 45 | // double mx,my; glfwGetCursorPos(iWindow, &mx, &my); 46 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 47 | 48 | color = color/255.0f; 49 | glClearColor(color, color, color, 1); 50 | glClear(GL_COLOR_BUFFER_BIT); 51 | } 52 | 53 | //! The main loop (called by emscripten for each frame) 54 | void main_loop(void *iUserData) 55 | { 56 | static bool terminated = false; 57 | 58 | if(terminated) 59 | { 60 | emscripten_cancel_main_loop(); 61 | return; 62 | } 63 | 64 | if(auto window = reinterpret_cast(iUserData); !glfwWindowShouldClose(window)) 65 | { 66 | // not done => renderFrame 67 | renderFrame(window); 68 | } 69 | else 70 | { 71 | // done => terminating 72 | glfwTerminate(); 73 | // deferring emscripten_cancel_main_loop() otherwise glfwTerminate() does not complete properly 74 | terminated = true; 75 | } 76 | } 77 | 78 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen 79 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 80 | { 81 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 82 | switch(key) { 83 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 84 | case GLFW_KEY_F: emscripten::glfw3::RequestFullscreen(window, false, true); break; // ok from a keyboard event 85 | default: break; 86 | } 87 | } 88 | } 89 | 90 | //! main 91 | int main() 92 | { 93 | if (!emscripten_supports_offscreencanvas()) 94 | { 95 | printf("This example requires OffscreenCanvas support\n"); 96 | return -1; 97 | } 98 | 99 | // set a callback for errors otherwise if there is a problem, we won't know 100 | glfwSetErrorCallback(consoleErrorHandler); 101 | 102 | // print the version on the console 103 | printf("%s\n", glfwGetVersionString()); 104 | 105 | // initialize the library 106 | if(!glfwInit()) 107 | return -1; 108 | 109 | // setting the association window <-> canvas 110 | emscripten::glfw3::SetNextWindowCanvasSelector("#canvas"); 111 | 112 | // create the only window 113 | auto window = glfwCreateWindow(600, 400, "Offscreen Canvas | emscripten-glfw", nullptr, nullptr); 114 | if(!window) 115 | return -1; 116 | 117 | // make context current 118 | glfwMakeContextCurrent(window); 119 | 120 | // makes the canvas resizable to the size of its div container 121 | emscripten::glfw3::MakeCanvasResizable(window, "#canvas-container"); 122 | 123 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 124 | glfwSetKeyCallback(window, onKeyChange); 125 | 126 | // tell emscripten to use "main_loop" as the main loop (window is user data) 127 | emscripten_set_main_loop_arg(main_loop, window, 0, GLFW_FALSE); 128 | } 129 | -------------------------------------------------------------------------------- /examples/example_offscreen_canvas/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | emscripten glfw 8 | 9 | 34 | 35 | 36 |

Welcome to Offscreen Canvas Example

37 |
38 |
39 |

Help

40 |
    41 |
  • Resize the canvas by resizing the browser window
  • 42 |
  • CTRL+Q to terminate
  • 43 |
  • CTRL+F or click the button for fullscreen
  • 44 |
  • Note that this example is using WebGL and is only changing the background color (no other rendering is happening)
  • 45 |
46 | 49 | {{{ SCRIPT }}} 50 | 51 | 52 | -------------------------------------------------------------------------------- /examples/example_pthread/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_pthread LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-pthread") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread -sEXIT_RUNTIME=1 -sPROXY_TO_PTHREAD -s SAFE_HEAP=1 -s ASSERTIONS=1 -s WASM=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | target_compile_options(${target} PUBLIC "-pthread") 15 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 16 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 17 | target_link_libraries(${target} PRIVATE glfw3_pthread) 18 | -------------------------------------------------------------------------------- /examples/example_pthread/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate how to use pthread which allows the code to run multiple threads concurrently 4 | 5 | ### Building instructions 6 | 7 | Using `emcc` from the command line (make sure it is in your path) 8 | 9 | ```sh 10 | mkdir build 11 | emcc -pthread --use-port=contrib.glfw3 -sEXIT_RUNTIME=1 -sPROXY_TO_PTHREAD --shell-file=shell.html main.cpp -o build/index.html 12 | ``` 13 | 14 | Using `CMake`: 15 | 16 | ```cmake 17 | set(target "example-pthread") 18 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread --use-port=contrib.glfw3 -sEXIT_RUNTIME=1 -sPROXY_TO_PTHREAD --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 19 | add_executable(${target} main.cpp) 20 | target_compile_options(${target} PUBLIC "-pthread") 21 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 22 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 23 | ``` 24 | 25 | ### Running instructions 26 | 27 | > [!NOTE] 28 | > Due to the [limitations](https://emscripten.org/docs/porting/pthreads.html) with browsers, this test needs to be run 29 | > with `emrun` which automatically takes care of the right headers. 30 | 31 | 32 | ```sh 33 | emrun --browser firefox build/main.html 34 | ``` -------------------------------------------------------------------------------- /examples/example_pthread/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | /** 28 | * The purpose of this example is to demonstrate how to use pthread. 29 | * It creates 2 threads that are incrementing a counter to simulate work on separate threads. 30 | * In total there are 4 threads: 31 | * - the main browser thread which executes `jsRenderFrame` and the key callback (`onKeyChange`) 32 | * - the `main` function which is its separate thread due to `-sPROXY_TO_PTHREAD` option 33 | * - two worker threads (the counters) started in `main` 34 | */ 35 | 36 | //! Display error message in the Console 37 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 38 | { 39 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 40 | } 41 | 42 | /** 43 | * For the sake of this example, uses the canvas2D api to display a message 44 | * Because the canvas is accessed, this needs to run on the main thread => using MAIN_THREAD_EM_ASM API 45 | */ 46 | void jsRenderFrame(GLFWwindow *glfwWindow, int w, int h, int fw, int fh, double mx, double my, int color, char const *c1, char const *c2) 47 | { 48 | MAIN_THREAD_EM_ASM( 49 | { 50 | const ctx = Module['glfwGetCanvas']($0).getContext('2d'); 51 | const w = $1; const h = $2; const fw = $3; const fh = $4; 52 | const color = $7; 53 | const c1 = UTF8ToString($8); const c2 = UTF8ToString($9); 54 | ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; 55 | ctx.fillRect(0, 0, fw, fh); // using framebuffer width/height 56 | ctx.save(); 57 | ctx.scale(fw/w, fh/h); 58 | const text = `${w}x${h} | ${$5}x${$6} | c1${c1} | c2${c2}`; 59 | ctx.font = '15px monospace'; 60 | ctx.fillStyle = `rgb(${255 - color}, 0, 0)`; 61 | ctx.fillText(text, 10 + (color / 4), 20 + color); 62 | ctx.restore(); 63 | }, glfwWindow, w, h, fw, fh, mx, my, color, c1, c2); 64 | } 65 | 66 | //! turns `std::this_thread::get_id()` into a string 67 | static std::string computeThreadId(std::thread::id id = std::this_thread::get_id()) 68 | { 69 | std::stringstream ss; 70 | ss << id; 71 | return ss.str(); 72 | } 73 | 74 | 75 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen 76 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 77 | { 78 | printf("onKeyChange running on thread: %s\n", computeThreadId().c_str()); 79 | 80 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 81 | switch(key) { 82 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 83 | case GLFW_KEY_F: emscripten_glfw_request_fullscreen(window, false, true); break; // ok from a keyboard event 84 | default: break; 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * A counter that keeps track on which thread the counting is happening */ 91 | struct Counter 92 | { 93 | unsigned long fValue{}; 94 | std::thread::id fThreadId{}; 95 | inline Counter inc() const { return {fValue + 1, std::this_thread::get_id()}; } 96 | std::string toString() const { 97 | std::stringstream ss; 98 | ss << "{" << fValue << ", " << fThreadId << "}"; 99 | return ss.str(); 100 | } 101 | }; 102 | 103 | /** 104 | * In this example, main is NOT executed on the browser main thread due to -sPROXY_TO_PTHREAD option */ 105 | int main() 106 | { 107 | // set a callback for errors otherwise if there is a problem, we won't know 108 | glfwSetErrorCallback(consoleErrorHandler); 109 | 110 | // print the version on the console 111 | printf("%s\n", glfwGetVersionString()); 112 | 113 | printf("main running on thread: %s\n", computeThreadId().c_str()); 114 | 115 | // initialize the library 116 | if(!glfwInit()) 117 | return -1; 118 | 119 | // no OpenGL (use canvas2D) 120 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 121 | 122 | // setting the association window <-> canvas 123 | emscripten_glfw_set_next_window_canvas_selector("#canvas"); 124 | 125 | // create the only window 126 | auto window = glfwCreateWindow(600, 400, "example_pthread | emscripten-glfw", nullptr, nullptr); 127 | if(!window) 128 | return -1; 129 | 130 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 131 | glfwSetKeyCallback(window, onKeyChange); 132 | 133 | std::atomic running{true}; 134 | std::atomic c1{}; 135 | std::atomic c2{}; 136 | 137 | std::thread t1([&c1, &running] { 138 | printf("t1 running on thread: %s\n", computeThreadId().c_str()); 139 | while(running) { 140 | c1.store(c1.load().inc()); 141 | std::this_thread::sleep_for(std::chrono::milliseconds(250)); 142 | } 143 | printf("t1 done on thread: %s\n", computeThreadId().c_str()); 144 | }); 145 | 146 | std::thread t2([&c2, &running] { 147 | printf("t2 running on thread: %s\n", computeThreadId().c_str()); 148 | while(running) { 149 | c2.store(c2.load().inc()); 150 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 151 | } 152 | printf("t2 done on thread: %s\n", computeThreadId().c_str()); 153 | }); 154 | 155 | int frameCount = 0; 156 | 157 | while(!glfwWindowShouldClose(window)) 158 | { 159 | glfwPollEvents(); 160 | 161 | int w,h; 162 | glfwGetWindowSize(window, &w, &h); 163 | int fw,fh; 164 | glfwGetFramebufferSize(window, &fw, &fh); 165 | double mx,my; 166 | glfwGetCursorPos(window, &mx, &my); 167 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 168 | jsRenderFrame(window, w, h, fw, fh, mx, my, static_cast(color), c1.load().toString().c_str(), c2.load().toString().c_str()); 169 | 170 | std::this_thread::sleep_for(std::chrono::milliseconds(16)); 171 | } 172 | 173 | running = false; 174 | 175 | glfwTerminate(); 176 | 177 | t1.join(); 178 | t2.join(); 179 | } 180 | -------------------------------------------------------------------------------- /examples/example_pthread/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | example_pthread | emscripten-glfw 8 | 9 | 26 | 27 | 28 |

Welcome to pthread Example

29 | 30 |
31 |

Help

32 |
    33 |
  • CTRL+Q to terminate
  • 34 |
  • CTRL+F or click the button for fullscreen
  • 35 |
36 | 39 | {{{ SCRIPT }}} 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/example_resizable_container/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_resizable_container LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-resizable-container") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 15 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 16 | target_link_libraries(${target} PRIVATE glfw3) 17 | -------------------------------------------------------------------------------- /examples/example_resizable_container/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate how to make the canvas resizable with another container (a 4 | surrounding `div`) driving its size. The container width is proportional to the size of the window and so as the 5 | window gets resized so does the `div` and so does the canvas. 6 | 7 | ### Building instructions 8 | 9 | Using `emcc` from the command line (make sure it is in your path) 10 | 11 | ```sh 12 | mkdir build 13 | emcc --use-port=contrib.glfw3 --shell-file=shell.html main.cpp -o build/index.html 14 | ``` 15 | 16 | Using `CMake`: 17 | 18 | ```cmake 19 | set(target "example-resizable-container") 20 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-port=contrib.glfw3 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 21 | add_executable(${target} main.cpp) 22 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 23 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 24 | ``` 25 | 26 | ### Running instructions 27 | 28 | ```sh 29 | # start a webserver on port 8000 30 | python3 -m http.server -d build 31 | ``` 32 | 33 | Run in your browser: http://localhost:8000 -------------------------------------------------------------------------------- /examples/example_resizable_container/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * The purpose of this example is to demonstrate how to make the canvas resizable with another container (a 26 | * surrounding div) driving its size. The container width is proportional to the size of the window and so as the 27 | * window gets resized so does the div and so does the canvas. */ 28 | 29 | //! Display error message in the Console 30 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 31 | { 32 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 33 | } 34 | 35 | //! jsRenderFrame: for the sake of this example, uses the canvas2D api to change the color of the screen / display a message 36 | EM_JS(void, jsRenderFrame, (GLFWwindow *glfwWindow, int w, int h, int fw, int fh, double mx, double my, int color, bool isFullscreen), { 37 | const ctx = Module['glfwGetCanvas'](glfwWindow).getContext('2d'); 38 | ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; 39 | ctx.fillRect(0, 0, fw, fh); // using framebuffer width/height 40 | const text = `${w}x${h} | ${mx}x${my} | CTRL+Q to terminate ${isFullscreen ? "" : '| CTRL+F for fullscreen'}`; 41 | ctx.font = '15px monospace'; 42 | ctx.fillStyle = `rgb(${255 - color}, 0, 0)`; 43 | ctx.fillText(text, 10 + color, 20 + color); 44 | }) 45 | 46 | //! Called for each frame 47 | void renderFrame(GLFWwindow *iWindow) 48 | { 49 | static int frameCount = 0; 50 | 51 | // poll events 52 | glfwPollEvents(); 53 | 54 | int w,h; glfwGetWindowSize(iWindow, &w, &h); 55 | int fw,fh; glfwGetFramebufferSize(iWindow, &fw, &fh); 56 | double mx,my; glfwGetCursorPos(iWindow, &mx, &my); 57 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 58 | jsRenderFrame(iWindow, w, h, fw, fh, mx, my, (int) color, emscripten::glfw3::IsWindowFullscreen(iWindow)); 59 | } 60 | 61 | //! The main loop (called by emscripten for each frame) 62 | void main_loop(void *iUserData) 63 | { 64 | if(auto window = reinterpret_cast(iUserData); !glfwWindowShouldClose(window)) 65 | { 66 | // not done => renderFrame 67 | renderFrame(window); 68 | } 69 | else 70 | { 71 | // done => terminating 72 | glfwTerminate(); 73 | emscripten_cancel_main_loop(); 74 | } 75 | } 76 | 77 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen 78 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 79 | { 80 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 81 | switch(key) { 82 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 83 | case GLFW_KEY_F: emscripten::glfw3::RequestFullscreen(window, false, true); break; // ok from a keyboard event 84 | default: break; 85 | } 86 | } 87 | } 88 | 89 | //! main 90 | int main() 91 | { 92 | // set a callback for errors otherwise if there is a problem, we won't know 93 | glfwSetErrorCallback(consoleErrorHandler); 94 | 95 | // print the version on the console 96 | printf("%s\n", glfwGetVersionString()); 97 | 98 | // initialize the library 99 | if(!glfwInit()) 100 | return -1; 101 | 102 | // no OpenGL (use canvas2D) 103 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 104 | 105 | // make it not Hi DPI Aware (simplify rendering code a bit) 106 | glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); 107 | 108 | // setting the association window <-> canvas 109 | emscripten::glfw3::SetNextWindowCanvasSelector("#canvas"); 110 | 111 | // create the only window 112 | auto window = glfwCreateWindow(600, 400, "Resizable Container | emscripten-glfw", nullptr, nullptr); 113 | if(!window) 114 | return -1; 115 | 116 | // makes the canvas resizable to the size of its div container 117 | emscripten::glfw3::MakeCanvasResizable(window, "#canvas-container"); 118 | 119 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 120 | glfwSetKeyCallback(window, onKeyChange); 121 | 122 | // tell emscripten to use "main_loop" as the main loop (window is user data) 123 | emscripten_set_main_loop_arg(main_loop, window, 0, GLFW_FALSE); 124 | } 125 | -------------------------------------------------------------------------------- /examples/example_resizable_container/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | emscripten glfw 8 | 9 | 34 | 35 | 36 |

Welcome to Resizable Container Example

37 |
38 |
39 |

Help

40 |
    41 |
  • Resize the canvas by resizing the browser window
  • 42 |
  • CTRL+Q to terminate
  • 43 |
  • CTRL+F or click the button for fullscreen
  • 44 |
45 | 48 | {{{ SCRIPT }}} 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/example_resizable_container_with_handle/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_resizable_container_with_handle LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-resizable-container-with-handle") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 15 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 16 | target_link_libraries(${target} PRIVATE glfw3) 17 | -------------------------------------------------------------------------------- /examples/example_resizable_container_with_handle/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate how to make the canvas resizable with a container that has a handle. 4 | The handle can be dragged around (left mouse drag) and the `div` is resized accordingly which in turn resizes the 5 | canvas, making the canvas truly resizable like a window. 6 | 7 | ### Building instructions 8 | 9 | Using `emcc` from the command line (make sure it is in your path) 10 | 11 | ```sh 12 | mkdir build 13 | emcc --use-port=contrib.glfw3 --shell-file=shell.html main.cpp -o build/index.html 14 | ``` 15 | 16 | Using `CMake`: 17 | 18 | ```cmake 19 | set(target "example-resizable-container-with-handle") 20 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-port=contrib.glfw3 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 21 | add_executable(${target} main.cpp) 22 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 23 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 24 | ``` 25 | 26 | ### Running instructions 27 | 28 | ```sh 29 | # start a webserver on port 8000 30 | python3 -m http.server -d build 31 | ``` 32 | 33 | Run in your browser: http://localhost:8000 -------------------------------------------------------------------------------- /examples/example_resizable_container_with_handle/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * The purpose of this example is to demonstrate how to make the canvas resizable with a container that has a handle. 26 | * The handle can be dragged around (left mouse drag) and the div is resized accordingly which in turn resizes the 27 | * canvas, making the canvas truly resizable like a window. */ 28 | 29 | //! Display error message in the Console 30 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 31 | { 32 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 33 | } 34 | 35 | //! jsRenderFrame: for the sake of this example, uses the canvas2D api to change the color of the screen / display a message 36 | EM_JS(void, jsRenderFrame, (GLFWwindow *glfwWindow, int w, int h, int fw, int fh, double mx, double my, int color, bool isFullscreen), { 37 | const ctx = Module['glfwGetCanvas'](glfwWindow).getContext('2d'); 38 | ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; ctx.fillRect(0, 0, fw, fh); // using framebuffer width/height 39 | const text = `${w}x${h} | ${mx}x${my} | CTRL+Q to terminate ${isFullscreen ? "" : '| CTRL+F for fullscreen'}`; 40 | ctx.font = '15px monospace'; ctx.fillStyle = `rgb(${255 - color}, 0, 0)`; ctx.fillText(text, 10 + color, 20 + color); 41 | }) 42 | 43 | //! Called for each frame 44 | void renderFrame(GLFWwindow *iWindow) 45 | { 46 | static int frameCount = 0; 47 | 48 | // poll events 49 | glfwPollEvents(); 50 | 51 | int w,h; glfwGetWindowSize(iWindow, &w, &h); 52 | int fw,fh; glfwGetFramebufferSize(iWindow, &fw, &fh); 53 | double mx,my; glfwGetCursorPos(iWindow, &mx, &my); 54 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 55 | jsRenderFrame(iWindow, w, h, fw, fh, mx, my, (int) color, emscripten_glfw_is_window_fullscreen(iWindow)); 56 | } 57 | 58 | //! The main loop (called by emscripten for each frame) 59 | void main_loop(void *iUserData) 60 | { 61 | if(auto window = reinterpret_cast(iUserData); !glfwWindowShouldClose(window)) 62 | { 63 | // not done => renderFrame 64 | renderFrame(window); 65 | } 66 | else 67 | { 68 | // done => terminating 69 | glfwTerminate(); 70 | emscripten_cancel_main_loop(); 71 | } 72 | } 73 | 74 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen 75 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 76 | { 77 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 78 | switch(key) { 79 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 80 | case GLFW_KEY_F: emscripten_glfw_request_fullscreen(window, false, true); break; // ok from a keyboard event 81 | default: break; 82 | } 83 | } 84 | } 85 | 86 | //! main 87 | int main() 88 | { 89 | // set a callback for errors otherwise if there is a problem, we won't know 90 | glfwSetErrorCallback(consoleErrorHandler); 91 | 92 | // print the version on the console 93 | printf("%s\n", glfwGetVersionString()); 94 | 95 | // initialize the library 96 | if(!glfwInit()) 97 | return -1; 98 | 99 | // no OpenGL (use canvas2D) 100 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 101 | 102 | // make it not Hi DPI Aware (simplify rendering code a bit) 103 | glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); 104 | 105 | // setting the association window <-> canvas 106 | emscripten_glfw_set_next_window_canvas_selector("#canvas"); 107 | 108 | // create the only window 109 | auto window = glfwCreateWindow(600, 400, "Resizable + Handle | emscripten-glfw", nullptr, nullptr); 110 | if(!window) 111 | return -1; 112 | 113 | // makes the canvas resizable to the size of its div container 114 | emscripten_glfw_make_canvas_resizable(window, "#canvas-container", "#canvas-handle"); 115 | 116 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 117 | glfwSetKeyCallback(window, onKeyChange); 118 | 119 | // tell emscripten to use "main_loop" as the main loop (window is user data) 120 | emscripten_set_main_loop_arg(main_loop, window, 0, GLFW_FALSE); 121 | } 122 | -------------------------------------------------------------------------------- /examples/example_resizable_container_with_handle/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | emscripten glfw 8 | 9 | 48 | 49 | 50 |

Welcome to Resizable Container With Handle Example

51 |
52 |
53 | 54 |
55 |
56 |

Help

57 |
    58 |
  • Use the handle to resize the window
  • 59 |
  • CTRL+Q to terminate
  • 60 |
  • CTRL+F or click the button for fullscreen
  • 61 |
62 | 65 | {{{ SCRIPT }}} 66 | 67 | 68 | -------------------------------------------------------------------------------- /examples/example_resizable_full_window/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_example_resizable_full_window LANGUAGES CXX) 4 | 5 | if(NOT EMSCRIPTEN) 6 | message(FATAL_ERROR "This example must be compiled with emscripten") 7 | endif() 8 | 9 | set(target "example-resizable-full-window") 10 | 11 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 12 | 13 | add_executable(${target} main.cpp) 14 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 15 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 16 | target_link_libraries(${target} PRIVATE glfw3) 17 | -------------------------------------------------------------------------------- /examples/example_resizable_full_window/README.md: -------------------------------------------------------------------------------- 1 | ### What does it do? 2 | 3 | The purpose of this example is to demonstrate how to make the canvas resizable and occupy the full window. 4 | 5 | ### Building instructions 6 | 7 | Using `emcc` from the command line (make sure it is in your path) 8 | 9 | ```sh 10 | mkdir build 11 | emcc --use-port=contrib.glfw3 --shell-file=shell.html main.cpp -o build/index.html 12 | ``` 13 | 14 | Using `CMake`: 15 | 16 | ```cmake 17 | set(target "example-resizable-full-window") 18 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --use-port=contrib.glfw3 --shell-file ${CMAKE_CURRENT_LIST_DIR}/shell.html") 19 | add_executable(${target} main.cpp) 20 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "index") 21 | set_target_properties(${target} PROPERTIES SUFFIX ".html") 22 | ``` 23 | 24 | ### Running instructions 25 | 26 | ```sh 27 | # start a webserver on port 8000 28 | python3 -m http.server -d build 29 | ``` 30 | 31 | Run in your browser: http://localhost:8000 -------------------------------------------------------------------------------- /examples/example_resizable_full_window/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | /** 25 | * The purpose of this example is to demonstrate how to make the canvas resizable and occupy the full window. */ 26 | 27 | //! Display error message in the Console 28 | void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 29 | { 30 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 31 | } 32 | 33 | //! jsRenderFrame: for the sake of this example, uses the canvas2D api to change the color of the screen / display a message 34 | EM_JS(void, jsRenderFrame, (GLFWwindow *glfwWindow, int w, int h, int fw, int fh, double mx, double my, int color, bool isFullscreen), { 35 | const ctx = Module['glfwGetCanvas'](glfwWindow).getContext('2d'); 36 | ctx.fillStyle = `rgb(${color}, ${color}, ${color})`; 37 | ctx.fillRect(0, 0, fw, fh); // using framebuffer width/height 38 | const text = `${w}x${h} | ${mx}x${my} | CTRL+Q to terminate ${isFullscreen ? "" : '| CTRL+F for fullscreen'}`; 39 | ctx.font = '15px monospace'; 40 | ctx.fillStyle = `rgb(${255 - color}, 0, 0)`; 41 | ctx.fillText(text, 10 + color, 20 + color); 42 | }) 43 | 44 | //! Called for each frame 45 | void renderFrame(GLFWwindow *iWindow) 46 | { 47 | static int frameCount = 0; 48 | 49 | // poll events 50 | glfwPollEvents(); 51 | 52 | int w,h; glfwGetWindowSize(iWindow, &w, &h); 53 | int fw,fh; glfwGetFramebufferSize(iWindow, &fw, &fh); 54 | double mx,my; glfwGetCursorPos(iWindow, &mx, &my); 55 | auto color = 127.0f + 127.0f * std::sin((float) frameCount++ / 120.f); 56 | jsRenderFrame(iWindow, w, h, fw, fh, mx, my, (int) color, emscripten_glfw_is_window_fullscreen(iWindow)); 57 | } 58 | 59 | //! The main loop (called by emscripten for each frame) 60 | void main_loop(void *iUserData) 61 | { 62 | if(auto window = reinterpret_cast(iUserData); !glfwWindowShouldClose(window)) 63 | { 64 | // not done => renderFrame 65 | renderFrame(window); 66 | } 67 | else 68 | { 69 | // done => terminating 70 | glfwTerminate(); 71 | emscripten_cancel_main_loop(); 72 | } 73 | } 74 | 75 | //! Handle key events => on CTRL+Q sets "window should close" flag, CTRL+F for fullscreen 76 | void onKeyChange(GLFWwindow* window, int key, int scancode, int action, int mods) 77 | { 78 | if(action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) { 79 | switch(key) { 80 | case GLFW_KEY_Q: glfwSetWindowShouldClose(window, GLFW_TRUE); break; 81 | case GLFW_KEY_F: emscripten_glfw_request_fullscreen(window, false, true); break; // ok from a keyboard event 82 | default: break; 83 | } 84 | } 85 | } 86 | 87 | //! main 88 | int main() 89 | { 90 | // set a callback for errors otherwise if there is a problem, we won't know 91 | glfwSetErrorCallback(consoleErrorHandler); 92 | 93 | // print the version on the console 94 | printf("%s\n", glfwGetVersionString()); 95 | 96 | // initialize the library 97 | if(!glfwInit()) 98 | return -1; 99 | 100 | // no OpenGL (use canvas2D) 101 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 102 | 103 | // make it not Hi DPI Aware (simplify rendering code a bit) 104 | glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); 105 | 106 | // setting the association window <-> canvas 107 | emscripten_glfw_set_next_window_canvas_selector("#canvas"); 108 | 109 | // create the only window 110 | auto window = glfwCreateWindow(320, 200, "Resizable Full Window | emscripten-glfw", nullptr, nullptr); 111 | if(!window) 112 | return -1; 113 | 114 | // makes the canvas resizable and match the full window size 115 | emscripten_glfw_make_canvas_resizable(window, "window", nullptr); 116 | 117 | // set callback for exit (CTRL+Q) and fullscreen (CTRL+F) 118 | glfwSetKeyCallback(window, onKeyChange); 119 | 120 | // tell emscripten to use "main_loop" as the main loop (window is user data) 121 | emscripten_set_main_loop_arg(main_loop, window, 0, GLFW_FALSE); 122 | } 123 | -------------------------------------------------------------------------------- /examples/example_resizable_full_window/shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | emscripten glfw 8 | 9 | 25 | 26 | 27 | 28 | 31 | {{{ SCRIPT }}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | emscripten glfw 6 | 25 | 26 | 27 |

emscripten glfw examples / demo

28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 73 | 74 | 75 |
DemoMain test/demo which demonstrates most features of the implementation
example_asyncifyThe purpose of this example is to demonstrate how to use asyncify which allows the code to be written like you 34 | would for a normal desktop application
example_hi_dpiThe purpose of this example is to demonstrate how to make the window Hi DPI aware
example_minimalThe purpose of this example is to be as minimal as possible: initializes glfw, create window, then destroy it and terminate glfw. 43 | Uses the default shell that comes with emscripten
example_resizable_containerThe purpose of this example is to demonstrate how to make the canvas resizable with another container (a 48 | surrounding div) driving its size. The container width is proportional to the size of the window and so as the 49 | window gets resized so does the div and so does the canvas
example_resizable_container_with_handleThe purpose of this example is to demonstrate how to make the canvas resizable with a container that has a handle. 54 | The handle can be dragged around (left mouse drag) and the div is resized accordingly which in turn resizes the 55 | canvas, making the canvas truly resizable like a window
example_resizable_full_windowThe purpose of this example is to demonstrate how to make the canvas resizable and occupy the full window
example_offscreen_canvasThe purpose of this example is to demonstrate how to use an offscreen canvas. 64 | Note that this test will only work if the proper headers are set and requires being run with emrun. 65 |
example_pthreadThe purpose of this example is to demonstrate how to use pthread. 70 | It creates 2 threads that are incrementing a counter to simulate work on separate threads. 71 | Note that this test will only work if the proper headers are set and requires being run with emrun. 72 |
76 | 77 | -------------------------------------------------------------------------------- /include/GLFW/emscripten_glfw3.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_EMSCRIPTEN_GLFW3_H 20 | #define EMSCRIPTEN_GLFW_EMSCRIPTEN_GLFW3_H 21 | 22 | #include 23 | #include 24 | 25 | //------------------------------------------------------------------------ 26 | // CPP Interface 27 | //------------------------------------------------------------------------ 28 | #ifdef __cplusplus 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace emscripten::glfw3 { 37 | 38 | /** 39 | * Before calling `glfwCreateWindow` you can communicate to the library which canvas to use by calling this function. 40 | * Conceptually this is similar to a window hint (must be called **prior** to creating the window). 41 | * 42 | * The parameter `canvasSelector` should be a (css path) selector referring to the canvas (ex: `#canvas`). 43 | * 44 | * By default, and to be backward compatible with the other emscripten ports, if this function is 45 | * not called, the library will use `Module["canvas"]`. 46 | * 47 | * If you want to create more than one window, you **must** call this function to specify which canvas to associate 48 | * to the window otherwise you will get a duplicate canvas error when creating the windows. */ 49 | void SetNextWindowCanvasSelector(std::string_view canvasSelector); 50 | 51 | /** 52 | * If you want the canvas (= window) size to be adjusted dynamically by the user you can call this 53 | * convenient function. Although you can implement this functionality yourself, the implementation can 54 | * be tricky to get right. 55 | * 56 | * Since this library takes charge of the size of the canvas, the idea behind this function is to specify which 57 | * other (html) element dictates the size of the canvas. The parameter `canvasResizeSelector` defines the 58 | * (css path) selector to this element. 59 | * 60 | * The 3 typical uses cases are: 61 | * 62 | * 1. the canvas fills the entire browser window, in which case the parameter `canvasResizeSelector` should simply 63 | * be set to "window" and the `handleSelector` is `std::nullopt` (which is the default). This use case can be 64 | * found in application like ImGui where the canvas is the window. 65 | * 66 | * Example code: 67 | * 68 | * ```html 69 | * 70 | * ``` 71 | * 72 | * ```cpp 73 | * emscripten::glfw3::SetNextWindowCanvasSelector("#canvas1"); 74 | * auto window = glfwCreateWindow(300, 200, "hello world", nullptr, nullptr); 75 | * emscripten::glfw3::MakeCanvasResizable(window, "window"); 76 | * ``` 77 | * 78 | * 2. the canvas is inside a `div`, in which case the `div` acts as a "container" and the `div` size is defined by 79 | * CSS rules, like for example: `width: 85%` so that when the page/browser gets resized, the `div` is resized 80 | * automatically, which then triggers the canvas to be resized. In this case, the parameter `canvasResizeSelector` 81 | * is the (css path) selector to this `div` and `handleSelector` is `std::nullopt` (default). 82 | * 83 | * Example code: 84 | * 85 | * ```html 86 | * 87 | *
88 | * ``` 89 | * 90 | * ```cpp 91 | * emscripten::glfw3::SetNextWindowCanvasSelector("#canvas1"); 92 | * auto window = glfwCreateWindow(300, 200, "hello world", nullptr, nullptr); 93 | * emscripten::glfw3::MakeCanvasResizable(window, "canvas1-container"); 94 | * ``` 95 | * 96 | * 3. same as 2. but the `div` is made resizable dynamically via a little "handle" (which ends up behaving like a 97 | * normal desktop window). 98 | * 99 | * Example code: 100 | * 101 | * ```html 102 | * 103 | * 104 | *
105 | * ``` 106 | * 107 | * ```cpp 108 | * emscripten::glfw3::SetNextWindowCanvasSelector("#canvas1"); 109 | * auto window = glfwCreateWindow(300, 200, "hello world", nullptr, nullptr); 110 | * emscripten::glfw3::MakeCanvasResizable(window, "canvas1-container", "canvas1-handle"); 111 | * ``` 112 | * 113 | * Note that there is an equivalent call added to `Module` that can be invoked from javascript: 114 | * `Module.glfwMakeCanvasResizable(...)`. 115 | * 116 | * @return `EMSCRIPTEN_RESULT_SUCCESS` if there was no issue, or an emscripten error code 117 | * otherwise (ex: a selector not referring to an existing element) */ 118 | int MakeCanvasResizable(GLFWwindow *window, 119 | std::string_view canvasResizeSelector, 120 | std::optional handleSelector = std::nullopt); 121 | 122 | /** 123 | * The opposite of `MakeCanvasResizable` 124 | * 125 | * @return `EMSCRIPTEN_RESULT_SUCCESS` if there was no issue, or an emscripten error code 126 | * otherwise (ex: not a valid window) */ 127 | int UnmakeCanvasResizable(GLFWwindow *window); 128 | 129 | /** 130 | * Returns `true` if the window is fullscreen, `false` otherwise */ 131 | bool IsWindowFullscreen(GLFWwindow *window); 132 | 133 | /** 134 | * Requests the window to go fullscreen. Note that due to browser restrictions, this function should only 135 | * be called from a user generated event (like a keyboard event or a mouse button press). 136 | * 137 | * Note that there is an equivalent call added to `Module` that can be invoked from javascript: 138 | * `Module.glfwRequestFullscreen(...)`. 139 | * 140 | * @param window which window to go fullscreen 141 | * @param lockPointer whether to lock the pointer or not 142 | * @param resizeCanvas whether to resize the canvas to match the fullscreen size or not 143 | * @return `EMSCRIPTEN_RESULT_SUCCESS` if there was no issue, or an emscripten error code otherwise */ 144 | int RequestFullscreen(GLFWwindow *window, bool lockPointer, bool resizeCanvas); 145 | 146 | /** 147 | * When the Super (`GLFW_KEY_LEFT_SUPER` or `GLFW_KEY_RIGHT_SUPER`) key is being held in a browser environment, 148 | * and any other key is being pressed, the up event for this key is never triggered. 149 | * This implementation tries to detect this scenario and implements a workaround. 150 | * This set of APIs lets you adjust the timeout used (default to 525ms/125ms). */ 151 | std::pair GetSuperPlusKeyTimeouts(); 152 | void SetSuperPlusKeyTimeouts(int timeoutMilliseconds, int repeatTimeoutMilliseconds); 153 | 154 | /** 155 | * Convenient call to open a url. 156 | * 157 | * @param target check https://developer.mozilla.org/en-US/docs/Web/API/Window/open for valid options */ 158 | void OpenURL(std::string_view url, std::optional target = std::nullopt); 159 | 160 | /** 161 | * @return `true` if running on an Apple platform only */ 162 | bool IsRuntimePlatformApple(); 163 | 164 | /** 165 | * By default, this library "swallows" (meaning calls `e.preventDefault()`) all keyboard events except for the 166 | * 3 keyboard shortcuts associated with cut, copy and paste (as returned by `GetPlatformBrowserKeyCallback()`). 167 | * 168 | * If you want to change this behavior, you can set your own callback: the callback is called on key down, repeat and 169 | * up and should return `true` for the event to bubble up (`e.preventDefault()` will **not** be called) so that 170 | * the browser can handle it as well. 171 | */ 172 | using browser_key_fun_t = std::function; 173 | browser_key_fun_t AddBrowserKeyCallback(browser_key_fun_t callback); 174 | browser_key_fun_t SetBrowserKeyCallback(browser_key_fun_t callback); 175 | browser_key_fun_t GetPlatformBrowserKeyCallback(); 176 | 177 | } // namespace emscripten::glfw3 178 | 179 | #endif // __cplusplus 180 | 181 | //------------------------------------------------------------------------ 182 | // C Interface 183 | //------------------------------------------------------------------------ 184 | 185 | #ifdef __cplusplus 186 | extern "C" { 187 | #endif 188 | 189 | #define GLFW_PLATFORM_EMSCRIPTEN 0x00060006 190 | 191 | /** 192 | * Before calling `glfwCreateWindow` you can communicate to the library which canvas to use by calling this function. 193 | * Conceptually this is similar to a window hint (must be called **prior** to creating the window). 194 | * 195 | * The parameter `canvasSelector` should be a (css path) selector referring to the canvas (ex: `#canvas`). 196 | * 197 | * By default, and to be backward compatible with the other emscripten ports, if this function is 198 | * not called, the library will use `Module["canvas"]`. 199 | * 200 | * If you want to create more than one window, you **must** call this function to specify which canvas to associate 201 | * to the window otherwise you will get a duplicate canvas error when creating the windows. */ 202 | void emscripten_glfw_set_next_window_canvas_selector(char const *canvasSelector); 203 | 204 | /** 205 | * If you want the canvas (= window) size to be adjusted dynamically by the user you can call this 206 | * convenient function. Although you can implement this functionality yourself, the implementation can 207 | * be tricky to get right. 208 | * 209 | * Since this library takes charge of the size of the canvas, the idea behind this function is to specify which 210 | * other (html) element dictates the size of the canvas. The parameter `canvasResizeSelector` defines the 211 | * (css path) selector to this element. 212 | * 213 | * The 3 typical uses cases are: 214 | * 215 | * 1. the canvas fills the entire browser window, in which case the parameter `canvasResizeSelector` should simply 216 | * be set to "window" and the `handleSelector` is `nullptr`. This use case can be found in application like ImGui 217 | * where the canvas is the window. 218 | * 219 | * Example code: 220 | * 221 | * ```html 222 | * 223 | * ``` 224 | * 225 | * ```cpp 226 | * emscripten_glfw_set_next_window_canvas_selector("#canvas1"); 227 | * auto window = glfwCreateWindow(300, 200, "hello world", nullptr, nullptr); 228 | * emscripten_glfw_make_canvas_resizable(window, "window", nullptr); 229 | * ``` 230 | * 231 | * 2. the canvas is inside a `div`, in which case the `div` acts as a "container" and the `div` size is defined by 232 | * CSS rules, like for example: `width: 85%` so that when the page/browser gets resized, the `div` is resized 233 | * automatically, which then triggers the canvas to be resized. In this case, the parameter `canvasResizeSelector` 234 | * is the (css path) selector to this `div` and `handleSelector` is `nullptr`. 235 | * 236 | * Example code: 237 | * 238 | * ```html 239 | * 240 | *
241 | * ``` 242 | * 243 | * ```cpp 244 | * emscripten_glfw_set_next_window_canvas_selector("#canvas1"); 245 | * auto window = glfwCreateWindow(300, 200, "hello world", nullptr, nullptr); 246 | * emscripten_glfw_make_canvas_resizable(window, "#canvas1-container", nullptr); 247 | * ``` 248 | * 249 | * 3. same as 2. but the `div` is made resizable dynamically via a little "handle" (which ends up behaving like a 250 | * normal desktop window). 251 | * 252 | * Example code: 253 | * 254 | * ```html 255 | * 256 | * 257 | *
258 | * ``` 259 | * 260 | * ```cpp 261 | * emscripten_glfw_set_next_window_canvas_selector("#canvas1"); 262 | * auto window = glfwCreateWindow(300, 200, "hello world", nullptr, nullptr); 263 | * emscripten_glfw_make_canvas_resizable(window, "#canvas1-container", "canvas1-handle"); 264 | * ``` 265 | * 266 | * Note that there is an equivalent call added to `Module` that can be invoked from javascript: 267 | * `Module.glfwMakeCanvasResizable(...)`. 268 | * 269 | * @return `EMSCRIPTEN_RESULT_SUCCESS` if there was no issue, or an emscripten error code 270 | * otherwise (ex: a selector not referring to an existing element) */ 271 | int emscripten_glfw_make_canvas_resizable(GLFWwindow *window, 272 | char const *canvasResizeSelector, 273 | char const *handleSelector); 274 | 275 | /** 276 | * The opposite of `emscripten_glfw_make_canvas_resizable` 277 | * 278 | * @return `EMSCRIPTEN_RESULT_SUCCESS` if there was no issue, or an emscripten error code 279 | * otherwise (ex: not a valid window) */ 280 | int emscripten_glfw_unmake_canvas_resizable(GLFWwindow *window); 281 | 282 | /** 283 | * Returns `EM_TRUE` if the window is fullscreen, `EM_FALSE` otherwise */ 284 | EM_BOOL emscripten_glfw_is_window_fullscreen(GLFWwindow *window); 285 | 286 | /** 287 | * Requests the window to go fullscreen. Note that due to browser restrictions, this function should only 288 | * be called from a user generated event (like a keyboard event or a mouse button press). 289 | * 290 | * Note that there is an equivalent call added to `Module` that can be invoked from javascript: 291 | * `Module.glfwRequestFullscreen(...)`. 292 | * 293 | * @param window which window to go fullscreen 294 | * @param lockPointer whether to lock the pointer or not 295 | * @param resizeCanvas whether to resize the canvas to match the fullscreen size or not 296 | * @return `EMSCRIPTEN_RESULT_SUCCESS` if there was no issue, or an emscripten error code otherwise */ 297 | int emscripten_glfw_request_fullscreen(GLFWwindow *window, EM_BOOL lockPointer, EM_BOOL resizeCanvas); 298 | 299 | /** 300 | * Convenient call to open a url 301 | * 302 | * @param target check https://developer.mozilla.org/en-US/docs/Web/API/Window/open for valid options 303 | * (`nullptr` is valid) */ 304 | void emscripten_glfw_open_url(char const *url, char const *target); 305 | 306 | /** 307 | * @return `true` if running on an Apple platform only */ 308 | EM_BOOL emscripten_glfw_is_runtime_platform_apple(); 309 | 310 | #ifdef __cplusplus 311 | } 312 | #endif 313 | 314 | #endif //EMSCRIPTEN_GLFW_EMSCRIPTEN_GLFW3_H 315 | -------------------------------------------------------------------------------- /include/GLFW/emscripten_glfw3_version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | #pragma once 19 | 20 | #ifndef EMSCRIPTEN_GLFW_VERSION_H 21 | #define EMSCRIPTEN_GLFW_VERSION_H 22 | 23 | // this version number is comparable and can be used in #if statements 24 | // #if EMSCRIPTEN_GLFW_VERSION >= 3'4'0'20240801 25 | // .... code that can be used past a certain release 26 | // #endif 27 | #define EMSCRIPTEN_GLFW_VERSION 3'4'0'20250927 28 | 29 | #define EMSCRIPTEN_GLFW_FULL_VERSION_STR "3.4.0.20250927" 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /port/emscripten-glfw3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 pongasoft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | # use this file except in compliance with the License. You may obtain a copy of 5 | # 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, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations under 13 | # the License. 14 | # 15 | # @author Yan Pujante 16 | 17 | TAG = '3.4.0.20250927' 18 | 19 | EXTERNAL_PORT = f'https://github.com/pongasoft/emscripten-glfw/releases/download/v{TAG}/emscripten-glfw3-{TAG}.zip' 20 | SHA512 = 'c1906c3e9356bf645b9d74115efb4f2029ab3e5bf5a60f18ec6a6a88c22e6374e7e388ef454f4c3c2e4b9b17c4482c04a9401885e956e2bad360acdb5157a35d' 21 | PORT_FILE = 'port/glfw3.py' 22 | 23 | # contrib port information (required) 24 | URL = 'https://github.com/pongasoft/emscripten-glfw' 25 | DESCRIPTION = 'This project is an emscripten port of GLFW 3.4 written in C++ for the web/webassembly platform' 26 | LICENSE = 'Apache 2.0 license' 27 | -------------------------------------------------------------------------------- /port/glfw3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-2025 pongasoft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | # use this file except in compliance with the License. You may obtain a copy of 5 | # 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, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations under 13 | # the License. 14 | # 15 | # @author Yan Pujante 16 | 17 | import os 18 | from typing import Union, Dict 19 | 20 | TAG = '@emscripten-glfw_VERSION@' 21 | 22 | # contrib port information (required) 23 | URL = 'https://github.com/pongasoft/emscripten-glfw' 24 | DESCRIPTION = 'This project is an emscripten port of GLFW 3.4 written in C++ for the web/webassembly platform' 25 | LICENSE = 'Apache 2.0 license' 26 | 27 | VALID_OPTION_VALUES = { 28 | 'disableWarning': ['true', 'false'], 29 | 'disableJoystick': ['true', 'false'], 30 | 'disableMultiWindow': ['true', 'false'], 31 | 'disableWebGL2': ['true', 'false'], 32 | 'optimizationLevel': ['0', '1', '2', '3', 'g', 's', 'z'] # all -OX possibilities 33 | } 34 | 35 | OPTIONS = { 36 | 'disableWarning': 'Boolean to disable warnings emitted by the library', 37 | 'disableJoystick': 'Boolean to disable support for joystick entirely', 38 | 'disableMultiWindow': 'Boolean to disable multi window support', 39 | 'disableWebGL2': 'Boolean to disable WebGL2 support', 40 | 'optimizationLevel': f'Optimization level: {VALID_OPTION_VALUES["optimizationLevel"]} (default to 2)', 41 | } 42 | 43 | # user options (from --use-port) 44 | opts: Dict[str, Union[str, bool]] = { 45 | 'disableWarning': False, 46 | 'disableJoystick': False, 47 | 'disableMultiWindow': False, 48 | 'disableWebGL2': False, 49 | 'optimizationLevel': '2' 50 | } 51 | 52 | port_name = 'emscripten-glfw3' 53 | 54 | # parent folder of where this file is located port/glfw3.py 55 | root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 56 | 57 | def get_lib_name(settings): 58 | return (f'lib_{port_name}_{TAG}-O{opts["optimizationLevel"]}' + 59 | ('-nw' if opts['disableWarning'] else '') + 60 | ('-nj' if opts['disableJoystick'] else '') + 61 | ('-sw' if opts['disableMultiWindow'] else '') + 62 | ('-mt' if settings.PTHREADS else '') + 63 | '.a') 64 | 65 | 66 | def get(ports, settings, shared): 67 | def create(final): 68 | source_path = os.path.join(root_path, 'src', 'cpp') 69 | source_include_paths = [os.path.join(root_path, 'external'), os.path.join(root_path, 'include')] 70 | 71 | flags = [f'-O{opts["optimizationLevel"]}'] 72 | 73 | if opts['disableWarning']: 74 | flags += ['-DEMSCRIPTEN_GLFW3_DISABLE_WARNING'] 75 | 76 | if opts['disableJoystick']: 77 | flags += ['-DEMSCRIPTEN_GLFW3_DISABLE_JOYSTICK'] 78 | 79 | if opts['disableMultiWindow']: 80 | flags += ['-DEMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT'] 81 | 82 | if settings.PTHREADS: 83 | flags += ['-pthread'] 84 | 85 | ports.build_port(source_path, final, port_name, includes=source_include_paths, flags=flags) 86 | 87 | return [shared.cache.get_lib(get_lib_name(settings), create, what='port')] 88 | 89 | 90 | def clear(ports, settings, shared): 91 | shared.cache.erase_lib(get_lib_name(settings)) 92 | 93 | 94 | def linker_setup(ports, settings): 95 | source_js_path = os.path.join(root_path, 'src', 'js', 'lib_emscripten_glfw3.js') 96 | settings.JS_LIBRARIES += [source_js_path] 97 | if not opts['disableWebGL2']: 98 | settings.MAX_WEBGL_VERSION = 2 99 | 100 | 101 | # Makes GLFW includes accessible without installing them (ex: #include ) 102 | def process_args(ports): 103 | args = [ 104 | '-I', os.path.join(root_path, 'external'), # 105 | '-I', os.path.join(root_path, 'include'), # 106 | f'-DEMSCRIPTEN_USE_PORT_CONTRIB_GLFW3={TAG.replace(".", "")}' 107 | ] 108 | return args 109 | 110 | 111 | def check_option(option, value, error_handler): 112 | if value not in VALID_OPTION_VALUES[option]: 113 | error_handler(f'[{option}] can be {list(VALID_OPTION_VALUES[option])}, got [{value}]') 114 | if isinstance(opts[option], bool): 115 | value = value == 'true' 116 | return value 117 | 118 | 119 | def handle_options(options, error_handler): 120 | for option, value in options.items(): 121 | opts[option] = check_option(option, value.lower(), error_handler) 122 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Clipboard.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include "Clipboard.h" 21 | 22 | extern "C" { 23 | void emglfw3c_set_clipboard_string(char const *iContent); 24 | } 25 | 26 | namespace emscripten::glfw3 { 27 | 28 | namespace clipboard { 29 | 30 | //------------------------------------------------------------------------ 31 | // clipboard::Timing::update 32 | //------------------------------------------------------------------------ 33 | void Timing::update() 34 | { 35 | fTime = emscripten_get_now(); 36 | } 37 | 38 | } 39 | 40 | //------------------------------------------------------------------------ 41 | // OSClipboard::update 42 | //------------------------------------------------------------------------ 43 | void OSClipboard::update(char const *iText, char const *iError) 44 | { 45 | fLastModified.update(); 46 | if(iText) 47 | { 48 | fText = iText; 49 | fError = std::nullopt; 50 | } 51 | if(iError) 52 | { 53 | fText = std::nullopt; 54 | fError = iError; 55 | } 56 | } 57 | 58 | //------------------------------------------------------------------------ 59 | // OSClipboard::writeText 60 | //------------------------------------------------------------------------ 61 | void OSClipboard::writeText(char const *iText) 62 | { 63 | if(iText) 64 | { 65 | update(iText, nullptr); 66 | emglfw3c_set_clipboard_string(iText); 67 | } 68 | } 69 | 70 | //------------------------------------------------------------------------ 71 | // Clipboard::setText 72 | //------------------------------------------------------------------------ 73 | void Clipboard::setText(char const *iText) 74 | { 75 | if(!iText) 76 | return; 77 | 78 | fLastModified.update(); 79 | fText = iText; 80 | fOSClipboard.writeText(iText); 81 | } 82 | 83 | //------------------------------------------------------------------------ 84 | // Clipboard::getText 85 | //------------------------------------------------------------------------ 86 | clipboard::text_t const &Clipboard::getText() const 87 | { 88 | if(!fOSClipboard.isUnknown() && fOSClipboard.getLastModified().fTime > fLastModified.fTime) 89 | return fOSClipboard.getText(); 90 | else 91 | return fText; 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Clipboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_CLIPBOARD_H 20 | #define EMSCRIPTEN_GLFW_CLIPBOARD_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace emscripten::glfw3 { 28 | 29 | namespace clipboard { 30 | 31 | struct Timing 32 | { 33 | void update(); 34 | double fTime{}; 35 | }; 36 | 37 | using text_t = std::optional; 38 | 39 | } 40 | 41 | class OSClipboard 42 | { 43 | public: 44 | constexpr bool isUnknown() const { return !fText.has_value(); } 45 | constexpr clipboard::Timing const &getLastModified() const { return fLastModified; } 46 | clipboard::text_t const &getText() const { return fText; } 47 | 48 | // navigator.clipboard.writeText 49 | void writeText(char const *iText); 50 | 51 | friend class Clipboard; 52 | 53 | private: 54 | void update(char const *iText, char const *iError); 55 | 56 | private: 57 | std::optional fText{}; 58 | std::optional fError{}; 59 | clipboard::Timing fLastModified{}; 60 | }; 61 | 62 | class Clipboard 63 | { 64 | public: 65 | void onClipboard(char const *iText, char const *iError) { fOSClipboard.update(iText, iError); }; 66 | 67 | void setText(char const *iText); 68 | clipboard::text_t const &getText() const; 69 | 70 | private: 71 | clipboard::text_t fText{}; 72 | clipboard::Timing fLastModified{}; 73 | 74 | OSClipboard fOSClipboard{}; 75 | }; 76 | 77 | } 78 | 79 | #endif //EMSCRIPTEN_GLFW_CLIPBOARD_H -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_CONFIG_H 20 | #define EMSCRIPTEN_GLFW_CONFIG_H 21 | 22 | #include 23 | #include 24 | 25 | namespace emscripten::glfw3 { 26 | 27 | using glfw_bool_t = int; 28 | 29 | constexpr inline glfw_bool_t toGlfwBool(bool iCBool) { return iCBool ? GLFW_TRUE : GLFW_FALSE; } 30 | constexpr inline EM_BOOL toEMBool(bool iCBool) { return iCBool ? EM_TRUE : EM_FALSE; } 31 | constexpr inline glfw_bool_t toGlfwBool(int iValue) { return iValue == GLFW_FALSE ? GLFW_FALSE : GLFW_TRUE; } 32 | constexpr inline bool toCBool(int iValue) { return toGlfwBool(iValue) != GLFW_FALSE; } 33 | constexpr inline char const *boolToString(bool b) { return b ? "true" : "false"; } 34 | 35 | struct Config 36 | { 37 | // For backward compatibility with emscripten, defaults to getting the canvas from Module 38 | static constexpr char const *kDefaultCanvasSelector = "Module['canvas']"; 39 | 40 | // GL Context 41 | int fClientAPI{GLFW_OPENGL_API}; // GLFW_CLIENT_API 42 | int fContextVersionMajor{1}; // GLFW_CONTEXT_VERSION_MAJOR 43 | int fContextVersionMinor{0}; // GLFW_CONTEXT_VERSION_MINOR 44 | 45 | // Window 46 | glfw_bool_t fScaleToMonitor{GLFW_FALSE}; // GLFW_SCALE_TO_MONITOR 47 | glfw_bool_t fScaleFramebuffer{GLFW_TRUE}; // GLFW_SCALE_FRAMEBUFFER 48 | glfw_bool_t fFocusOnShow{GLFW_TRUE}; // GLFW_FOCUS_ON_SHOW 49 | glfw_bool_t fResizable{GLFW_TRUE}; // GLFW_RESIZABLE 50 | glfw_bool_t fVisible{GLFW_TRUE}; // GLFW_VISIBLE 51 | glfw_bool_t fFocused{GLFW_TRUE}; // GLFW_FOCUSED 52 | std::string fCanvasSelector{kDefaultCanvasSelector}; // GLFW_EMSCRIPTEN_CANVAS_SELECTOR 53 | 54 | // Framebuffer 55 | int fAlphaBits {8}; // GLFW_ALPHA_BITS 56 | int fDepthBits {24}; // GLFW_DEPTH_BITS 57 | int fStencilBits {8}; // GLFW_STENCIL_BITS 58 | int fSamples {0}; // GLFW_SAMPLES 59 | }; 60 | 61 | } 62 | 63 | #endif //EMSCRIPTEN_GLFW_CONFIG_H 64 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Context.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_CONTEXT_H 20 | #define EMSCRIPTEN_GLFW_CONTEXT_H 21 | 22 | #include 23 | #include 24 | #include "Window.h" 25 | #include "Monitor.h" 26 | #include "Clipboard.h" 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_JOYSTICK 34 | #include "Joystick.h" 35 | #endif 36 | 37 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT 38 | #include 39 | #endif 40 | 41 | namespace emscripten::glfw3 { 42 | 43 | // in javascript the performance/DOMHighResTimeStamp measures in milliseconds 44 | constexpr static uint64_t kTimerFrequency = 1000; 45 | 46 | class Context 47 | { 48 | public: 49 | static std::unique_ptr init(); 50 | ~Context(); 51 | 52 | public: 53 | void terminate(); 54 | void defaultWindowHints() { fConfig = {}; } 55 | void setWindowHint(int iHint, int iValue); 56 | void setWindowHint(int iHint, char const *iValue); 57 | 58 | // window 59 | GLFWwindow* createWindow(int iWidth, int iHeight, const char* iTitle, GLFWmonitor* iMonitor, GLFWwindow* iShare); 60 | void destroyWindow(GLFWwindow *iWindow); 61 | std::shared_ptr getWindow(GLFWwindow *iWindow) const; 62 | void setWindowTitle(GLFWwindow *iWindow, char const *iTitle); 63 | char const *getWindowTitle(GLFWwindow *iWindow) const; 64 | void setNextWindowCanvasSelector(char const *iCanvasSelector); 65 | 66 | void makeContextCurrent(GLFWwindow* iWindow); 67 | GLFWwindow* getCurrentContext() const; 68 | 69 | // monitor 70 | GLFWmonitor** getMonitors(int* oCount); 71 | GLFWmonitor* getPrimaryMonitor() const; 72 | void getMonitorPos(GLFWmonitor* iMonitor, int* oXPos, int* oYPos) const; 73 | void getMonitorWorkArea(GLFWmonitor* iMonitor, int* oXPos, int* oYPos, int* oWidth, int* oHeight) const; 74 | void getMonitorContentScale(GLFWmonitor* iMonitor, float *oXScale, float* oYScale) const; 75 | std::shared_ptr getMonitor(GLFWmonitor *iMonitor) const; 76 | GLFWmonitorfun setMonitorCallback(GLFWmonitorfun iCallback) { return std::exchange(fMonitorCallback, iCallback); } 77 | GLFWmonitor *getMonitor(GLFWwindow *iWindow) const; 78 | 79 | // cursor 80 | GLFWcursor *createStandardCursor(int iShape); 81 | GLFWcursor* createCursor(GLFWimage const *iImage, int iXHot, int iYHot); 82 | void destroyCursor(GLFWcursor *iCursor); 83 | void setCursor(GLFWwindow *iWindow, GLFWcursor *iCursor); 84 | 85 | // joystick 86 | GLFWjoystickfun setJoystickCallback(GLFWjoystickfun iCallback) { return std::exchange(fJoystickCallback, iCallback); } 87 | 88 | // time 89 | double getTimeInSeconds() const; 90 | void setTimeInSeconds(double iValue); 91 | static uint64_t getTimerValue() ; 92 | 93 | // events 94 | void pollEvents(); 95 | void swapInterval(int iInterval) const; 96 | 97 | // opengl 98 | glfw_bool_t isExtensionSupported(const char* extension); 99 | 100 | // clipboard 101 | void setClipboardString(char const *iContent); 102 | char const *getClipboardString(); 103 | 104 | // keyboard 105 | Keyboard::SuperPlusKeyTimeout getSuperPlusKeyTimeout() const { return fSuperPlusKeyTimeout; } 106 | void setSuperPlusKeyTimeout(Keyboard::SuperPlusKeyTimeout const &iTimeout) { fSuperPlusKeyTimeout = iTimeout; } 107 | browser_key_fun_t setBrowserKeyCallback(browser_key_fun_t iCallback) { return std::exchange(fBrowserKeyCallback, std::move(iCallback)); } 108 | browser_key_fun_t getBrowserKeyCallback() const { return fBrowserKeyCallback; } 109 | 110 | // misc 111 | void openURL(std::string_view url, std::optional target); 112 | bool isRuntimePlatformApple() const; 113 | 114 | public: 115 | void onScaleChange(); 116 | void onWindowResize(GLFWwindow *iWindow, int iWidth, int iHeight); 117 | void onClipboard(char const *iText, char const *iError) { fClipboard.onClipboard(iText, iError); }; 118 | int requestFullscreen(GLFWwindow *iWindow, bool iLockPointer, bool iResizeCanvas); 119 | int requestPointerLock(GLFWwindow *iWindow); 120 | void requestPointerUnlock(GLFWwindow *iWindow, glfw_cursor_mode_t iCursorMode); 121 | void onFocus(GLFWwindow *iWindow) { fLastKnownFocusedWindow = iWindow; } 122 | bool onKeyDown(Keyboard::Event const &iEvent); 123 | bool onKeyUp(Keyboard::Event const &iEvent); 124 | 125 | friend class Window; 126 | 127 | private: 128 | enum class InputType { kUnknown, kMouse, kTouch }; 129 | private: 130 | Context(); 131 | std::shared_ptr findWindow(GLFWwindow *iWindow) const; 132 | std::shared_ptr findMonitor(GLFWmonitor *iMonitor) const; 133 | std::shared_ptr findCustomCursor(GLFWcursor *iCursor) const; 134 | void addOrRemoveEventListeners(bool iAdd); 135 | bool onEnterFullscreen(EmscriptenFullscreenChangeEvent const *iEvent); 136 | bool onExitFullscreen(); 137 | bool onPointerLock(EmscriptenPointerlockChangeEvent const *iEvent); 138 | bool onPointerUnlock(); 139 | std::shared_ptr findFocusedOrSingleWindow() const; 140 | void computeWindowPos(); 141 | 142 | // touch 143 | constexpr bool isTrackingTouch() const { return fTouchPointId.has_value(); } 144 | bool onTouchStart(GLFWwindow *iOriginWindow, EmscriptenTouchEvent const *iEvent); 145 | bool onTouchMove(EmscriptenTouchEvent const *iEvent); 146 | bool onTouchEnd(EmscriptenTouchEvent const *iEvent); 147 | EmscriptenTouchPoint const *findTouchPoint(EmscriptenTouchEvent const *iEvent) const; 148 | 149 | static double getPlatformTimerValue(); 150 | 151 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_JOYSTICK 152 | bool onGamepadConnectionChange(EmscriptenGamepadEvent const *iEvent); 153 | #endif 154 | 155 | private: 156 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT 157 | std::vector> fWindows{}; 158 | #else 159 | std::shared_ptr fSingleWindow{}; 160 | #endif 161 | GLFWwindow *fCurrentWindowOpaquePtr{}; 162 | std::shared_ptr fCurrentWindow{}; // window made current via glfwMakeContextCurrent 163 | std::shared_ptr fCurrentMonitor{new Monitor{}}; 164 | GLFWwindow *fLastKnownFocusedWindow{}; 165 | Config fConfig{}; 166 | float fScale{1.0f}; 167 | double fInitialTime{getPlatformTimerValue()}; 168 | InputType fInputType{InputType::kUnknown}; 169 | 170 | // clipboard 171 | Clipboard fClipboard{}; 172 | 173 | std::optional fFullscreenRequest{}; 174 | std::optional fPointerLockRequest{}; 175 | std::optional fPointerUnlockRequest{}; 176 | 177 | GLFWmonitorfun fMonitorCallback{}; 178 | GLFWjoystickfun fJoystickCallback{}; 179 | 180 | // mouse 181 | EventListener fOnMouseMove{}; 182 | EventListener fOnMouseButtonUp{}; 183 | 184 | // touch 185 | EventListener fOnTouchStart{}; 186 | EventListener fOnTouchMove{}; 187 | EventListener fOnTouchCancel{}; 188 | EventListener fOnTouchEnd{}; 189 | std::optional fTouchPointId{}; 190 | 191 | EventListener fOnFullscreenChange{}; 192 | EventListener fOnPointerLockChange{}; 193 | EventListener fOnPointerLockError{}; 194 | std::vector> fCustomCursors{}; 195 | 196 | // keyboard 197 | browser_key_fun_t fBrowserKeyCallback{}; 198 | Keyboard::SuperPlusKeyTimeout fSuperPlusKeyTimeout{525, 125}; // milliseconds 199 | 200 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_JOYSTICK 201 | int fPresentJoystickCount{}; 202 | EventListener fOnGamepadConnected{}; 203 | EventListener fOnGamepadDisconnected{}; 204 | #endif 205 | }; 206 | 207 | } 208 | 209 | #endif //EMSCRIPTEN_GLFW_CONTEXT_H -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Cursor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_CURSOR_H 20 | #define EMSCRIPTEN_GLFW_CURSOR_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include "Object.h" 26 | 27 | namespace emscripten::glfw3 { 28 | 29 | using glfw_cursor_shape_t = int; 30 | 31 | class Cursor : public Object 32 | { 33 | public: 34 | virtual void set(GLFWwindow *iWindow) const = 0; 35 | }; 36 | 37 | class StandardCursor : public Cursor 38 | { 39 | public: 40 | constexpr StandardCursor(glfw_cursor_shape_t iShape, char const *iCSSValue) : fShape{iShape}, fCSSValue{iCSSValue} {} 41 | 42 | void set(GLFWwindow *iWindow) const override; 43 | 44 | static std::shared_ptr findCursor(glfw_cursor_shape_t iShape) 45 | { 46 | auto i = std::find_if(kCursors.begin(), kCursors.end(), [iShape](auto &c) { return c->fShape == iShape; }); 47 | return i == kCursors.end() ? nullptr : *i; 48 | } 49 | 50 | static std::shared_ptr findCursor(GLFWcursor *iCursor) 51 | { 52 | if(!iCursor) 53 | return getDefault(); 54 | 55 | auto i = std::find_if(kCursors.begin(), kCursors.end(), [iCursor](auto &c) { return c->asOpaquePtr() == iCursor; }); 56 | return i == kCursors.end() ? nullptr : *i; 57 | } 58 | 59 | static std::shared_ptr getDefault() { return findCursor(GLFW_ARROW_CURSOR); } 60 | static std::shared_ptr getHiddenCursor() { return kCursorHidden; } 61 | 62 | private: 63 | static const std::shared_ptr kCursorHidden; 64 | 65 | public: 66 | glfw_cursor_shape_t fShape{}; 67 | char const *fCSSValue{}; 68 | 69 | public: 70 | static const std::array, 10> kCursors; 71 | }; 72 | 73 | class CustomCursor : public Cursor 74 | { 75 | public: 76 | CustomCursor(int iXHot, int iYHot) : fXHot{iXHot}, fYHot{iYHot} {} 77 | void set(GLFWwindow *iWindow) const override; 78 | 79 | private: 80 | int fXHot; 81 | int fYHot; 82 | }; 83 | 84 | } 85 | 86 | #endif //EMSCRIPTEN_GLFW_CURSOR_H 87 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/ErrorHandler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include "ErrorHandler.h" 20 | #include 21 | 22 | namespace emscripten::glfw3 { 23 | 24 | //------------------------------------------------------------------------ 25 | // ErrorHandler::getErrorHandler 26 | //------------------------------------------------------------------------ 27 | ErrorHandler &ErrorHandler::instance() 28 | { 29 | static ErrorHandler kInstance{}; 30 | return kInstance; 31 | } 32 | 33 | //------------------------------------------------------------------------ 34 | // ErrorHandler::setErrorCallback 35 | //------------------------------------------------------------------------ 36 | GLFWerrorfun ErrorHandler::setErrorCallback(GLFWerrorfun iCallback) 37 | { 38 | std::swap(iCallback, fErrorCallback); 39 | return iCallback; 40 | } 41 | 42 | //------------------------------------------------------------------------ 43 | // ErrorHandler::popError 44 | //------------------------------------------------------------------------ 45 | int ErrorHandler::popError(char const **iDescription) 46 | { 47 | int res = fLastErrorCode; 48 | if(fLastErrorCode != GLFW_NO_ERROR && iDescription) 49 | { 50 | fLastErrorCode = GLFW_NO_ERROR; 51 | *iDescription = fLastErrorMessage.data(); 52 | } 53 | return res; 54 | } 55 | 56 | //------------------------------------------------------------------------ 57 | // ErrorHandler::doLogError 58 | //------------------------------------------------------------------------ 59 | void ErrorHandler::doLogError(int iErrorCode, char const *iErrorMessage) 60 | { 61 | fLastErrorCode = iErrorCode; 62 | fLastErrorMessage = iErrorMessage; 63 | if(fErrorCallback) 64 | fErrorCallback(fLastErrorCode, fLastErrorMessage.data()); 65 | } 66 | 67 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_WARNING 68 | //------------------------------------------------------------------------ 69 | // ErrorHandler::doLogWarning 70 | //------------------------------------------------------------------------ 71 | void ErrorHandler::doLogWarning(char const *iWarningMessage) 72 | { 73 | if(fErrorCallback) 74 | { 75 | std::string msg = "[Warning] " + std::string(iWarningMessage); 76 | if(std::find(fWarningMessages.begin(), fWarningMessages.end(), msg) == fWarningMessages.end()) 77 | { 78 | // we want to avoid repeating the same warning message over and over 79 | fErrorCallback(GLFW_NO_ERROR, msg.data()); 80 | fWarningMessages.emplace(std::move(msg)); 81 | } 82 | } 83 | } 84 | #endif 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/ErrorHandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_ERRORHANDLER_H 20 | #define EMSCRIPTEN_GLFW_ERRORHANDLER_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace emscripten::glfw3 { 28 | 29 | class ErrorHandler 30 | { 31 | public: 32 | GLFWerrorfun setErrorCallback(GLFWerrorfun iCallback); 33 | int popError(const char** iDescription); 34 | 35 | template 36 | void logError(int iErrorCode, char const *iErrorMessage, Args... args); 37 | 38 | template 39 | void logWarning(char const *iWarningMessage, Args... args); 40 | 41 | public: 42 | static ErrorHandler &instance(); 43 | 44 | private: 45 | void doLogError(int iErrorCode, char const *iErrorMessage); 46 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_WARNING 47 | void doLogWarning(char const *iWarningMessage); 48 | #endif 49 | 50 | private: 51 | GLFWerrorfun fErrorCallback{}; 52 | int fLastErrorCode{GLFW_NO_ERROR}; 53 | std::string fLastErrorMessage{}; 54 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_WARNING 55 | std::set fWarningMessages{}; 56 | #endif 57 | }; 58 | 59 | //------------------------------------------------------------------------ 60 | // ErrorHandler::logError 61 | //------------------------------------------------------------------------ 62 | template 63 | void ErrorHandler::logError(int iErrorCode, char const *iErrorMessage, Args... args) 64 | { 65 | if constexpr(sizeof...(args) > 0) 66 | { 67 | constexpr int kMessageSize = 1024; 68 | char message[kMessageSize]; 69 | std::snprintf(message, sizeof(message), iErrorMessage, args ...); 70 | message[sizeof(message) - 1] = '\0'; 71 | doLogError(iErrorCode, message); 72 | } 73 | else 74 | doLogError(iErrorCode, iErrorMessage); 75 | } 76 | 77 | //------------------------------------------------------------------------ 78 | // ErrorHandler::logWarning 79 | //------------------------------------------------------------------------ 80 | template 81 | void ErrorHandler::logWarning(char const *iWarningMessage, Args... args) 82 | { 83 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_WARNING 84 | if constexpr(sizeof...(args) > 0) 85 | { 86 | constexpr int kMessageSize = 1024; 87 | char message[kMessageSize]; 88 | std::snprintf(message, sizeof(message), iWarningMessage, args ...); 89 | message[sizeof(message) - 1] = '\0'; 90 | doLogWarning(message); 91 | } 92 | else 93 | doLogWarning(iWarningMessage); 94 | #endif 95 | } 96 | 97 | 98 | } 99 | 100 | #endif //EMSCRIPTEN_GLFW_ERRORHANDLER_H 101 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Events.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2025 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include "Events.h" 20 | 21 | namespace emscripten::glfw3 { 22 | 23 | //------------------------------------------------------------------------ 24 | // EventListenerBase::setTarget 25 | //------------------------------------------------------------------------ 26 | void EventListenerBase::setTarget(char const *iTarget) 27 | { 28 | if(iTarget == EMSCRIPTEN_EVENT_TARGET_WINDOW) 29 | { 30 | fSpecialTarget = EMSCRIPTEN_EVENT_TARGET_WINDOW; 31 | fTarget = "window"; 32 | } else if(iTarget == EMSCRIPTEN_EVENT_TARGET_DOCUMENT) 33 | { 34 | fSpecialTarget = EMSCRIPTEN_EVENT_TARGET_DOCUMENT; 35 | fTarget = "document"; 36 | } else if(iTarget == EMSCRIPTEN_EVENT_TARGET_SCREEN) 37 | { 38 | fSpecialTarget = EMSCRIPTEN_EVENT_TARGET_SCREEN; 39 | fTarget = "screen"; 40 | } else { 41 | fTarget = iTarget; 42 | } 43 | } 44 | 45 | //------------------------------------------------------------------------ 46 | // EventListenerBase::setCallbackFunction 47 | //------------------------------------------------------------------------ 48 | bool EventListenerBase::setCallbackFunction(callback_function_t iFunction) 49 | { 50 | remove(); 51 | 52 | fCallbackFunction = std::move(iFunction); 53 | 54 | auto error = fCallbackFunction(true); 55 | 56 | if(error != EMSCRIPTEN_RESULT_SUCCESS) 57 | { 58 | ErrorHandler::instance().logError(GLFW_PLATFORM_ERROR, "Error [%d] while registering listener for [%s]", 59 | error, 60 | fTarget.c_str()); 61 | fCallbackFunction = {}; 62 | return false; 63 | } 64 | 65 | return true; 66 | } 67 | 68 | //------------------------------------------------------------------------ 69 | // EventListenerBase::remove 70 | //------------------------------------------------------------------------ 71 | void EventListenerBase::remove() 72 | { 73 | if(fCallbackFunction) 74 | { 75 | auto error = fCallbackFunction(false); 76 | if(error != EMSCRIPTEN_RESULT_SUCCESS) 77 | { 78 | ErrorHandler::instance().logError(GLFW_PLATFORM_ERROR, "Error [%d] while removing listener for [%s]", 79 | error, 80 | fTarget.c_str()); 81 | } 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Events.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_EVENTS_H 20 | #define EMSCRIPTEN_GLFW_EVENTS_H 21 | 22 | #include 23 | #include 24 | #include "ErrorHandler.h" 25 | 26 | namespace emscripten::glfw3 { 27 | 28 | //! EmscriptenEventCallback 29 | template 30 | using EmscriptenEventCallback = EM_BOOL (*)(int, E const *, void *); 31 | 32 | //! EmscriptenCallbackFunction 33 | template 34 | using EmscriptenCallbackFunction = EMSCRIPTEN_RESULT (*)(const char *, void *, EM_BOOL, EmscriptenEventCallback, pthread_t); 35 | 36 | //! EmscriptenCallbackFunction2 (implicit target) 37 | template 38 | using EmscriptenCallbackFunction2 = EMSCRIPTEN_RESULT (*)(void *, EM_BOOL, EmscriptenEventCallback, pthread_t); 39 | 40 | class EventListenerBase 41 | { 42 | public: 43 | virtual ~EventListenerBase() { remove(); }; 44 | 45 | void remove(); 46 | 47 | protected: 48 | /** 49 | * Wraps the call to an Emscripten setting callback function (ex: emscripten_set_mousemove_callback_on_thread) 50 | * in a generic fashion. The `bool` parameter is `true` for "add", `false` for "remove" 51 | */ 52 | using callback_function_t = std::function; 53 | 54 | protected: 55 | void setTarget(char const *iTarget); 56 | bool setCallbackFunction(callback_function_t iFunction); 57 | 58 | protected: 59 | char const *fSpecialTarget{}; 60 | std::string fTarget{}; 61 | pthread_t fThread{EM_CALLBACK_THREAD_CONTEXT_CALLING_THREAD}; 62 | 63 | private: 64 | callback_function_t fCallbackFunction{}; 65 | }; 66 | 67 | template 68 | class EventListener : public EventListenerBase 69 | { 70 | public: 71 | using event_listener_t = std::function; 72 | 73 | public: 74 | 75 | inline EventListener &target(char const *iTarget) { setTarget(iTarget); return *this; } 76 | inline EventListener &listener(event_listener_t v) { fEventListener = std::move(v); return *this; } 77 | 78 | bool add(EmscriptenCallbackFunction iFunction); 79 | bool add(EmscriptenCallbackFunction2 iFunction); 80 | 81 | bool invoke(int iEventType, E const *iEvent) { if(fEventListener) return fEventListener(iEventType, iEvent); else return false; } 82 | 83 | private: 84 | event_listener_t fEventListener{}; 85 | }; 86 | 87 | //------------------------------------------------------------------------ 88 | // EventListenerCallback 89 | // - generic callback which extracts EventListener from iUserData and invoke it 90 | //------------------------------------------------------------------------ 91 | template 92 | bool EventListenerCallback(int iEventType, E const *iEvent, void *iUserData) 93 | { 94 | auto cb = reinterpret_cast *>(iUserData); 95 | return cb->invoke(iEventType, iEvent); 96 | } 97 | 98 | //------------------------------------------------------------------------ 99 | // EventListener::add 100 | //------------------------------------------------------------------------ 101 | template 102 | bool EventListener::add(EmscriptenCallbackFunction iFunction) 103 | { 104 | return setCallbackFunction([iFunction, this](bool iAdd) { 105 | return iFunction(fSpecialTarget ? fSpecialTarget : fTarget.c_str(), 106 | this, 107 | EM_FALSE, 108 | iAdd ? EventListenerCallback : nullptr, 109 | fThread); 110 | }); 111 | } 112 | 113 | //------------------------------------------------------------------------ 114 | // EventListener::add 115 | //------------------------------------------------------------------------ 116 | template 117 | bool EventListener::add(EmscriptenCallbackFunction2 iFunction) 118 | { 119 | return setCallbackFunction([iFunction, this](bool iAdd) { 120 | return iFunction(this, 121 | EM_FALSE, 122 | iAdd ? EventListenerCallback : nullptr, 123 | fThread); 124 | }); 125 | } 126 | 127 | 128 | } 129 | #endif //EMSCRIPTEN_GLFW_EVENTS_H 130 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Joystick.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include "Joystick.h" 20 | #include "ErrorHandler.h" 21 | 22 | namespace emscripten::glfw3 { 23 | 24 | //------------------------------------------------------------------------ 25 | // Joystick::kJoysticks 26 | //------------------------------------------------------------------------ 27 | std::array Joystick::kJoysticks{ 28 | Joystick{GLFW_JOYSTICK_1}, 29 | Joystick{GLFW_JOYSTICK_2}, 30 | Joystick{GLFW_JOYSTICK_3}, 31 | Joystick{GLFW_JOYSTICK_4}, 32 | Joystick{GLFW_JOYSTICK_5}, 33 | Joystick{GLFW_JOYSTICK_6}, 34 | Joystick{GLFW_JOYSTICK_7}, 35 | Joystick{GLFW_JOYSTICK_8}, 36 | Joystick{GLFW_JOYSTICK_9}, 37 | Joystick{GLFW_JOYSTICK_10}, 38 | Joystick{GLFW_JOYSTICK_11}, 39 | Joystick{GLFW_JOYSTICK_12}, 40 | Joystick{GLFW_JOYSTICK_13}, 41 | Joystick{GLFW_JOYSTICK_14}, 42 | Joystick{GLFW_JOYSTICK_15}, 43 | Joystick{GLFW_JOYSTICK_16} 44 | }; 45 | 46 | //------------------------------------------------------------------------ 47 | // Joystick::checkValidJoystick 48 | //------------------------------------------------------------------------ 49 | bool Joystick::checkValidJoystick(glfw_joystick_id_t id) 50 | { 51 | if(id >= GLFW_JOYSTICK_1 && id <= GLFW_JOYSTICK_LAST) 52 | return true; 53 | 54 | ErrorHandler::instance().logError(GLFW_INVALID_VALUE, "invalid joystick id [%d]", id); 55 | return false; 56 | } 57 | 58 | //------------------------------------------------------------------------ 59 | // Joystick::connect 60 | //------------------------------------------------------------------------ 61 | void Joystick::connect(EmscriptenGamepadEvent const *iEvent) 62 | { 63 | fPresent = true; 64 | fName = iEvent->id; 65 | fMapping = iEvent->mapping; 66 | } 67 | 68 | //------------------------------------------------------------------------ 69 | // Joystick::disconnect 70 | //------------------------------------------------------------------------ 71 | void Joystick::disconnect(EmscriptenGamepadEvent const *iEvent) 72 | { 73 | fPresent = false; 74 | fName = {}; 75 | fMapping = {}; 76 | } 77 | 78 | //------------------------------------------------------------------------ 79 | // Joystick::populate 80 | //------------------------------------------------------------------------ 81 | void Joystick::populate(EmscriptenGamepadEvent const *iEvent) 82 | { 83 | // making sure the 2 arrays are the same size 84 | static_assert(std::tuple_size{} == std::tuple_size{}); 85 | 86 | fNumButtons = std::clamp(iEvent->numButtons, 0, static_cast(fDigitalButtons.size())); 87 | for(auto i = 0; i < fNumButtons; i++) 88 | { 89 | fDigitalButtons[i] = iEvent->digitalButton[i]; 90 | } 91 | 92 | for(auto i = 0; i < fNumButtons; i++) 93 | { 94 | fAnalogButtons[i] = static_cast(iEvent->analogButton[i]); 95 | } 96 | 97 | fNumAxes = std::clamp(iEvent->numAxes, 0, static_cast(fAxes.size())); 98 | for(auto i = 0; i < fNumAxes; i++) 99 | { 100 | fAxes[i] = static_cast(iEvent->axis[i]); 101 | } 102 | } 103 | 104 | //------------------------------------------------------------------------ 105 | // Joystick::getAxes 106 | //------------------------------------------------------------------------ 107 | float const *Joystick::getAxes(int *oCount) const 108 | { 109 | if(fPresent) 110 | { 111 | *oCount = fNumAxes; 112 | return fAxes.data(); 113 | } 114 | else 115 | { 116 | *oCount = 0; 117 | return nullptr; 118 | } 119 | } 120 | 121 | //------------------------------------------------------------------------ 122 | // Joystick::getDigitalButtons 123 | //------------------------------------------------------------------------ 124 | glfw_joystick_button_state_t const *Joystick::getDigitalButtons(int *oCount) const 125 | { 126 | if(fPresent) 127 | { 128 | *oCount = fNumButtons; 129 | return fDigitalButtons.data(); 130 | } 131 | else 132 | { 133 | *oCount = 0; 134 | return nullptr; 135 | } 136 | } 137 | 138 | //------------------------------------------------------------------------ 139 | // Joystick::getHats 140 | //------------------------------------------------------------------------ 141 | glfw_joystick_button_state_t const *Joystick::getHats(int *oCount) const 142 | { 143 | static_assert(std::tuple_size{} >= kNumGamepadButtons); 144 | 145 | // see https://w3c.github.io/gamepad/#remapping for DPad buttons 146 | if(isGamepad() && fNumButtons >= kNumGamepadButtons) 147 | { 148 | *oCount = 1; 149 | fDPad = 0; 150 | if(fDigitalButtons[12]) fDPad |= GLFW_HAT_UP; 151 | if(fDigitalButtons[13]) fDPad |= GLFW_HAT_DOWN; 152 | if(fDigitalButtons[14]) fDPad |= GLFW_HAT_LEFT; 153 | if(fDigitalButtons[15]) fDPad |= GLFW_HAT_RIGHT; 154 | return &fDPad; 155 | } 156 | else 157 | { 158 | *oCount = 0; 159 | return nullptr; 160 | } 161 | } 162 | 163 | //------------------------------------------------------------------------ 164 | // Joystick::getGamepadState 165 | //------------------------------------------------------------------------ 166 | int Joystick::getGamepadState(GLFWgamepadstate *oState) const 167 | { 168 | static_assert(std::tuple_size{} >= kNumGamepadAxes); 169 | static_assert(std::tuple_size{} >= kNumGamepadButtons); 170 | static_assert(std::tuple_size{} >= kNumGamepadButtons); 171 | 172 | if(isGamepad() && fNumAxes >= kNumGamepadAxes && fNumButtons >= kNumGamepadButtons) 173 | { 174 | // axes 175 | oState->axes[GLFW_GAMEPAD_AXIS_LEFT_X] = fAxes[0]; 176 | oState->axes[GLFW_GAMEPAD_AXIS_LEFT_Y] = fAxes[1]; 177 | oState->axes[GLFW_GAMEPAD_AXIS_RIGHT_X] = fAxes[2]; 178 | oState->axes[GLFW_GAMEPAD_AXIS_RIGHT_Y] = fAxes[3]; 179 | oState->axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] = (fAnalogButtons[6] * 2.0f) - 1.0f; // [0,1] => [-1,1] 180 | oState->axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] = (fAnalogButtons[7] * 2.0f) - 1.0f; // [0,1] => [-1,1] 181 | 182 | // buttons 183 | oState->buttons[GLFW_GAMEPAD_BUTTON_A] = fDigitalButtons[0]; 184 | oState->buttons[GLFW_GAMEPAD_BUTTON_B] = fDigitalButtons[1]; 185 | oState->buttons[GLFW_GAMEPAD_BUTTON_X] = fDigitalButtons[2]; 186 | oState->buttons[GLFW_GAMEPAD_BUTTON_Y] = fDigitalButtons[3]; 187 | oState->buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] = fDigitalButtons[4]; 188 | oState->buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] = fDigitalButtons[5]; 189 | oState->buttons[GLFW_GAMEPAD_BUTTON_BACK] = fDigitalButtons[8]; 190 | oState->buttons[GLFW_GAMEPAD_BUTTON_START] = fDigitalButtons[9]; 191 | oState->buttons[GLFW_GAMEPAD_BUTTON_GUIDE] = fDigitalButtons[16]; 192 | oState->buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB] = fDigitalButtons[10]; 193 | oState->buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB] = fDigitalButtons[11]; 194 | oState->buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP] = fDigitalButtons[12]; 195 | oState->buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] = fDigitalButtons[15]; 196 | oState->buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN] = fDigitalButtons[13]; 197 | oState->buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT] = fDigitalButtons[14]; 198 | 199 | return GLFW_TRUE; 200 | } 201 | else 202 | return GLFW_FALSE; 203 | } 204 | 205 | //------------------------------------------------------------------------ 206 | // Joystick::computePresentJoystickCount 207 | //------------------------------------------------------------------------ 208 | int Joystick::computePresentJoystickCount() 209 | { 210 | return std::count_if(kJoysticks.begin(), kJoysticks.end(), [](auto &j) { return j.fPresent; }); 211 | } 212 | 213 | //------------------------------------------------------------------------ 214 | // Joystick::pollJoysticks 215 | //------------------------------------------------------------------------ 216 | int Joystick::pollJoysticks() 217 | { 218 | int count = 0; 219 | if(emscripten_sample_gamepad_data() == EMSCRIPTEN_RESULT_SUCCESS) 220 | { 221 | for(auto &j: kJoysticks) 222 | { 223 | if(j.isPresent()) 224 | { 225 | count++; 226 | EmscriptenGamepadEvent data; 227 | if(emscripten_get_gamepad_status(j.fId, &data) == EMSCRIPTEN_RESULT_SUCCESS) 228 | j.populate(&data); 229 | } 230 | } 231 | } 232 | return count; 233 | } 234 | 235 | 236 | 237 | } -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Joystick.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_JOYSTICK_H 20 | #define EMSCRIPTEN_GLFW_JOYSTICK_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace emscripten::glfw3 { 28 | 29 | using glfw_joystick_id_t = int; 30 | using glfw_joystick_button_state_t = unsigned char; 31 | 32 | class Joystick 33 | { 34 | public: 35 | explicit Joystick(glfw_joystick_id_t id) noexcept : fId{id} {} 36 | 37 | constexpr bool isPresent() const { return fPresent; } 38 | inline bool isGamepad() const { return fPresent && fMapping == "standard"; } 39 | void *getUserPointer() const { return fUserPointer; } 40 | void setUserPointer(void* userPointer) { fUserPointer = userPointer; } 41 | char const *getName() const { return fPresent ? fName.data() : nullptr; } 42 | char const *getMapping() const { return fPresent ? fMapping.data() : nullptr; } 43 | float const *getAxes(int *oCount) const; 44 | glfw_joystick_button_state_t const *getDigitalButtons(int *oCount) const; 45 | glfw_joystick_button_state_t const *getHats(int *oCount) const; 46 | int getGamepadState(GLFWgamepadstate *oState) const; 47 | 48 | static bool checkValidJoystick(glfw_joystick_id_t id); 49 | static Joystick &getJoystick(glfw_joystick_id_t id) { return kJoysticks.at(id); } 50 | static Joystick *findJoystick(glfw_joystick_id_t id) { return checkValidJoystick(id) ? &kJoysticks[id] : nullptr; } 51 | 52 | friend class Context; 53 | 54 | private: 55 | void connect(EmscriptenGamepadEvent const *iEvent); 56 | void disconnect(EmscriptenGamepadEvent const *iEvent); 57 | void populate(EmscriptenGamepadEvent const *iEvent); 58 | 59 | static int computePresentJoystickCount(); 60 | static int pollJoysticks(); 61 | 62 | private: 63 | static std::array kJoysticks; 64 | 65 | // Javascript gamepad API has 4 axes and 16 buttons 66 | // mapping is defined here https://w3c.github.io/gamepad/#remapping 67 | constexpr static size_t kNumGamepadAxes{4}; 68 | constexpr static size_t kNumGamepadButtons{16}; 69 | constexpr static auto kEmscriptenMaxNumDigitalButtons = std::extent_v; 70 | constexpr static auto kEmscriptenMaxNumAnalogButtons = std::extent_v; 71 | constexpr static auto kEmscriptenMaxNumAxes = std::extent_v; 72 | 73 | private: 74 | glfw_joystick_id_t fId{}; 75 | bool fPresent{}; 76 | void *fUserPointer{}; 77 | std::string fName{}; 78 | std::string fMapping{}; 79 | int fNumAxes{}; 80 | int fNumButtons{}; 81 | std::array fAxes{}; 82 | std::array fDigitalButtons{}; 83 | std::array fAnalogButtons{}; 84 | mutable glfw_joystick_button_state_t fDPad{}; 85 | }; 86 | 87 | } 88 | 89 | #endif //EMSCRIPTEN_GLFW_JOYSTICK_H -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Keyboard.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include "Keyboard.h" 20 | #include "Config.h" 21 | #include "ErrorHandler.h" 22 | 23 | namespace emscripten::glfw3 { 24 | 25 | //------------------------------------------------------------------------ 26 | // Keyboard::getKeyName 27 | //------------------------------------------------------------------------ 28 | const char *Keyboard::getKeyName(glfw_key_t iKey, glfw_scancode_t iScancode) 29 | { 30 | auto scancode = iKey == GLFW_KEY_UNKNOWN ? iScancode : getKeyScancode(iKey); 31 | return keyboard::scancodeToString(scancode); 32 | } 33 | 34 | //------------------------------------------------------------------------ 35 | // Keyboard::getKeyState 36 | //------------------------------------------------------------------------ 37 | glfw_key_state_t Keyboard::getKeyState(glfw_key_t iKey) 38 | { 39 | if(iKey < 0 || iKey > GLFW_KEY_LAST) 40 | { 41 | ErrorHandler::instance().logError(GLFW_INVALID_VALUE, "Invalid key [%d]", iKey); 42 | return GLFW_RELEASE; 43 | } 44 | 45 | if(fKeyStates[iKey] == kStickyPress) 46 | { 47 | fKeyStates[iKey] = GLFW_RELEASE; 48 | return GLFW_PRESS; 49 | } 50 | 51 | return fKeyStates[iKey]; 52 | } 53 | 54 | //------------------------------------------------------------------------ 55 | // Keyboard::computeModifierBits 56 | //------------------------------------------------------------------------ 57 | int Keyboard::computeModifierBits() const 58 | { 59 | int bits = 0; 60 | if(isShiftPressed()) 61 | bits |= GLFW_MOD_SHIFT; 62 | if(isControlPressed()) 63 | bits |= GLFW_MOD_CONTROL; 64 | if(isAltPressed()) 65 | bits |= GLFW_MOD_ALT; 66 | if(isSuperPressed()) 67 | bits |= GLFW_MOD_SUPER; 68 | 69 | // TODO need to implement my own event callback... to call e.getModifierState("CapsLock") and e.getModifierState("NumLock") 70 | // if(iKeyboardEvent && fInputModeLockKeyMods) 71 | // { 72 | // if(toCBool(iKeyboardEvent->capsLockKey)) 73 | // bits |= GLFW_MOD_CAPS_LOCK; 74 | // if(toCBool(iKeyboardEvent->numLockKey)) 75 | // bits |= GLFW_MOD_NUM_LOCK; 76 | // } 77 | return bits; 78 | } 79 | 80 | //------------------------------------------------------------------------ 81 | // Keyboard::onKeyDown 82 | //------------------------------------------------------------------------ 83 | bool Keyboard::onKeyDown(GLFWwindow *iWindow, Event const &iEvent, emscripten::glfw3::browser_key_fun_t const &iBrowserKeyCallback) 84 | { 85 | auto scancode = getKeyScancode(iEvent.code); 86 | glfw_key_t key = getGLFWKey(scancode); 87 | 88 | // handling Super 89 | if(iEvent.isSuperPressed()) 90 | { 91 | if(handleSuperKeyPressed(key, iEvent.repeat)) 92 | return true; 93 | } 94 | 95 | auto handled = false; 96 | 97 | if(key != GLFW_KEY_UNKNOWN) 98 | { 99 | fKeyStates[key] = GLFW_PRESS; 100 | 101 | if(fKeyCallback) 102 | fKeyCallback(iWindow, key, scancode, iEvent.repeat ? GLFW_REPEAT : GLFW_PRESS, iEvent.modifierBits); 103 | 104 | handled = !iBrowserKeyCallback || 105 | !iBrowserKeyCallback(iWindow, key, scancode, iEvent.repeat ? GLFW_REPEAT : GLFW_PRESS, iEvent.modifierBits); 106 | } 107 | 108 | if(fCharCallback) 109 | { 110 | if(iEvent.codepoint > 0) 111 | fCharCallback(iWindow, iEvent.codepoint); 112 | } 113 | 114 | return handled; 115 | } 116 | 117 | //------------------------------------------------------------------------ 118 | // Keyboard::onKeyUp 119 | //------------------------------------------------------------------------ 120 | bool Keyboard::onKeyUp(GLFWwindow *iWindow, Event const &iEvent, emscripten::glfw3::browser_key_fun_t const &iBrowserKeyCallback) 121 | { 122 | auto scancode = getKeyScancode(iEvent.code); 123 | glfw_key_t key = getGLFWKey(scancode); 124 | 125 | if(key == GLFW_KEY_UNKNOWN) 126 | return false; 127 | 128 | fSuperPlusKeys.erase(key); 129 | 130 | const glfw_key_state_t state = GLFW_RELEASE; 131 | 132 | bool wasSuperPressed = isSuperPressed(); 133 | 134 | auto handled = false; 135 | 136 | if(fKeyStates[key] != GLFW_RELEASE && fKeyStates[key] != kStickyPress) 137 | { 138 | fKeyStates[key] = fStickyKeys ? kStickyPress : state; 139 | 140 | if(fKeyCallback) 141 | fKeyCallback(iWindow, key, scancode, state, iEvent.modifierBits); 142 | 143 | handled = !iBrowserKeyCallback || 144 | !iBrowserKeyCallback(iWindow, key, scancode, state, iEvent.modifierBits); 145 | 146 | } 147 | 148 | if(wasSuperPressed && !isSuperPressed()) 149 | resetKeysOnSuperRelease(iWindow); 150 | 151 | return handled; 152 | } 153 | 154 | 155 | //------------------------------------------------------------------------ 156 | // Keyboard::resetKey 157 | //------------------------------------------------------------------------ 158 | void Keyboard::resetKey(GLFWwindow *iWindow, glfw_key_t iKey, int modifierBits) 159 | { 160 | if(fKeyStates[iKey] != GLFW_RELEASE && fKeyStates[iKey] != kStickyPress) 161 | { 162 | fKeyStates[iKey] = GLFW_RELEASE; 163 | 164 | if(fKeyCallback) 165 | fKeyCallback(iWindow, iKey, getKeyScancode(iKey), GLFW_RELEASE, modifierBits); 166 | } 167 | } 168 | 169 | //------------------------------------------------------------------------ 170 | // Keyboard::resetAllKeys 171 | //------------------------------------------------------------------------ 172 | void Keyboard::resetAllKeys(GLFWwindow *iWindow) 173 | { 174 | for(auto key = 0; key < fKeyStates.size(); key++) 175 | { 176 | resetKey(iWindow, key, 0); 177 | } 178 | fSuperPlusKeys.clear(); 179 | } 180 | 181 | //------------------------------------------------------------------------ 182 | // Keyboard::setStickyKeys 183 | //------------------------------------------------------------------------ 184 | void Keyboard::setStickyKeys(bool iStickyKeys) 185 | { 186 | fStickyKeys = iStickyKeys; 187 | if(!fStickyKeys) 188 | { 189 | for(auto &state: fKeyStates) 190 | { 191 | if(state == kStickyPress) 192 | state = GLFW_RELEASE; 193 | } 194 | } 195 | } 196 | 197 | //------------------------------------------------------------------------ 198 | // Keyboard::handleSuperKeyPressed 199 | // Called when the super key is involved in a key down event (`e.metaKey` is `true`) 200 | //------------------------------------------------------------------------ 201 | bool Keyboard::handleSuperKeyPressed(glfw_key_t iKey, bool iRepeat) 202 | { 203 | // case Super + Key (Special keys behave differently, so we ignore them) 204 | if(!isSpecialKey(iKey)) 205 | { 206 | // the issue is that the Up event is NEVER received... so we work around in multiple ways 207 | if(fSuperPlusKeys.find(iKey) == fSuperPlusKeys.end() && iRepeat) 208 | { 209 | // case when we have issued a key up event due to timeout... so we are ignoring 210 | return true; 211 | } 212 | // we store the current time (processed in handleSuperPlusKeys) 213 | fSuperPlusKeys[iKey] = {static_cast(emscripten_get_now()), iRepeat}; 214 | } 215 | 216 | // case when it is the super key itself: some keys might already be down 217 | if(isSuperKey(iKey) && !iRepeat) 218 | { 219 | for(int k = 0; k < fKeyStates.size(); k++) 220 | { 221 | if(!isSpecialKey(k) && fKeyStates[k] != GLFW_RELEASE) 222 | { 223 | fSuperPlusKeys[k] = {static_cast(emscripten_get_now()), false}; 224 | } 225 | } 226 | } 227 | 228 | return false; 229 | } 230 | 231 | //------------------------------------------------------------------------ 232 | // Keyboard::resetKeysOnSuperRelease 233 | // Called when the Super key has been released to reset all keys that were down 234 | //------------------------------------------------------------------------ 235 | void Keyboard::resetKeysOnSuperRelease(GLFWwindow *iWindow) 236 | { 237 | auto modifierBits = computeModifierBits(); 238 | 239 | for(auto &[key, _]: fSuperPlusKeys) 240 | { 241 | resetKey(iWindow, key, modifierBits); 242 | } 243 | 244 | fSuperPlusKeys.clear(); 245 | } 246 | 247 | //------------------------------------------------------------------------ 248 | // Keyboard::handleSuperPlusKeys 249 | // Called every frame when fSuperPlusKeys is not empty to purge keys that have timed out 250 | //------------------------------------------------------------------------ 251 | void Keyboard::handleSuperPlusKeys(GLFWwindow *iWindow, SuperPlusKeyTimeout const &iTimeout) 252 | { 253 | auto modifierBits = computeModifierBits(); 254 | 255 | auto now = static_cast(emscripten_get_now()); 256 | 257 | for(auto it = fSuperPlusKeys.begin(); it != fSuperPlusKeys.end();) 258 | { 259 | auto timeout = it->second.fRepeat ? iTimeout.fRepeatTimeout : iTimeout.fTimeout; 260 | if(it->second.fLastTimePressed + timeout <= now) 261 | { 262 | resetKey(iWindow, it->first, modifierBits); 263 | it = fSuperPlusKeys.erase(it); 264 | } 265 | else 266 | { 267 | ++it; 268 | } 269 | } 270 | } 271 | 272 | } -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Keyboard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_KEYBOARD_H 20 | #define EMSCRIPTEN_GLFW_KEYBOARD_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "KeyboardMapping.h" 28 | 29 | namespace emscripten::glfw3 { 30 | 31 | using glfw_key_state_t = int; // ex: GLFW_RELEASE 32 | 33 | class Keyboard 34 | { 35 | public: 36 | struct Event 37 | { 38 | char const *code; 39 | char const *key; 40 | bool repeat; 41 | int codepoint; 42 | int modifierBits; 43 | 44 | constexpr bool isSuperPressed() const { return modifierBits & GLFW_MOD_SUPER; } 45 | }; 46 | 47 | struct SuperPlusKeyTimeout 48 | { 49 | int fTimeout; 50 | int fRepeatTimeout; 51 | }; 52 | 53 | public: 54 | glfw_key_state_t getKeyState(glfw_key_t iKey); 55 | // bool isKeyPressed(glfw_key_t iKey) const { return getKeyState(iKey) != GLFW_RELEASE; } 56 | constexpr bool isShiftPressed() const { return isValidKeyPressed(GLFW_KEY_LEFT_SHIFT) || isValidKeyPressed(GLFW_KEY_RIGHT_SHIFT); } 57 | constexpr bool isControlPressed() const { return isValidKeyPressed(GLFW_KEY_LEFT_CONTROL) || isValidKeyPressed(GLFW_KEY_RIGHT_CONTROL); } 58 | constexpr bool isAltPressed() const { return isValidKeyPressed(GLFW_KEY_LEFT_ALT) || isValidKeyPressed(GLFW_KEY_RIGHT_ALT); } 59 | constexpr bool isSuperPressed() const { return isValidKeyPressed(GLFW_KEY_LEFT_SUPER) || isValidKeyPressed(GLFW_KEY_RIGHT_SUPER); } 60 | 61 | inline GLFWkeyfun setKeyCallback(GLFWkeyfun iCallback) { return std::exchange(fKeyCallback, iCallback); } 62 | inline GLFWcharfun setCharCallback(GLFWcharfun iCallback) { return std::exchange(fCharCallback, iCallback); } 63 | 64 | void setInputModeLockKeyMods(bool iValue) { fInputModeLockKeyMods = iValue; } 65 | int computeModifierBits() const; 66 | 67 | bool onKeyDown(GLFWwindow *iWindow, Event const &iEvent, emscripten::glfw3::browser_key_fun_t const &iBrowserKeyCallback); 68 | bool onKeyUp(GLFWwindow *iWindow, Event const &iEvent, emscripten::glfw3::browser_key_fun_t const &iBrowserKeyCallback); 69 | void resetAllKeys(GLFWwindow *iWindow); 70 | inline bool hasSuperPlusKeys() const { return !fSuperPlusKeys.empty(); } 71 | void handleSuperPlusKeys(GLFWwindow *iWindow, SuperPlusKeyTimeout const &iTimeout); 72 | 73 | void setStickyKeys(bool iStickyKeys); 74 | bool getStickyKeys() const { return fStickyKeys; }; 75 | 76 | public: 77 | static constexpr glfw_scancode_t getKeyScancode(glfw_key_t iKey) { return keyboard::keyCodeToScancode(iKey); } 78 | static const char* getKeyName(glfw_key_t iKey, glfw_scancode_t iScancode); 79 | 80 | private: 81 | static constexpr glfw_key_state_t kStickyPress = 3; 82 | static constexpr glfw_key_t getGLFWKey(glfw_scancode_t iScancode) { return keyboard::scancodeToKeyCode(iScancode); } 83 | static constexpr glfw_scancode_t getKeyScancode(char const *iKeyboardEventCode) { return keyboard::keyboardEventCodeToScancode(iKeyboardEventCode); } 84 | 85 | // constexpr when we know that the key is a valid key 86 | constexpr glfw_key_state_t getValidKeyState(glfw_key_t iKey) const { return fKeyStates[iKey]; } 87 | constexpr bool isValidKeyPressed(glfw_key_t iKey) const { return getValidKeyState(iKey) != GLFW_RELEASE; } 88 | static constexpr bool isSpecialKey(glfw_key_t iKey) { 89 | return iKey == GLFW_KEY_LEFT_SHIFT || iKey == GLFW_KEY_RIGHT_SHIFT || 90 | iKey == GLFW_KEY_LEFT_CONTROL || iKey == GLFW_KEY_RIGHT_CONTROL || 91 | iKey == GLFW_KEY_LEFT_ALT || iKey == GLFW_KEY_RIGHT_ALT || 92 | iKey == GLFW_KEY_LEFT_SUPER || iKey == GLFW_KEY_RIGHT_SUPER; 93 | } 94 | static constexpr bool isSuperKey(glfw_key_t iKey) { 95 | return iKey == GLFW_KEY_LEFT_SUPER || iKey == GLFW_KEY_RIGHT_SUPER; 96 | } 97 | 98 | void resetKey(GLFWwindow *iWindow, glfw_key_t iKey, int modifierBits); 99 | void resetKeysOnSuperRelease(GLFWwindow *iWindow); 100 | bool handleSuperKeyPressed(glfw_key_t iKey, bool iRepeat); 101 | 102 | private: 103 | struct SuperPlusKeyTiming 104 | { 105 | int fLastTimePressed{}; 106 | bool fRepeat{}; 107 | }; 108 | private: 109 | std::array fKeyStates{GLFW_RELEASE}; 110 | std::map fSuperPlusKeys{}; 111 | bool fInputModeLockKeyMods{}; 112 | bool fStickyKeys{}; 113 | GLFWkeyfun fKeyCallback{}; 114 | GLFWcharfun fCharCallback{}; 115 | }; 116 | 117 | 118 | } 119 | 120 | #endif //EMSCRIPTEN_GLFW_KEYBOARD_H 121 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Monitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_MONITOR_H 20 | #define EMSCRIPTEN_GLFW_MONITOR_H 21 | 22 | #include 23 | #include "Object.h" 24 | 25 | namespace emscripten::glfw3 { 26 | 27 | class Monitor : public Object 28 | { 29 | public: 30 | // user pointer 31 | inline void *getUserPointer() const { return fUserPointer; } 32 | inline void setUserPointer(void *iPointer) { fUserPointer = iPointer; } 33 | char const *getName() const { return "Browser"; } 34 | 35 | private: 36 | void *fUserPointer{}; 37 | }; 38 | 39 | } 40 | 41 | #endif //EMSCRIPTEN_GLFW_MONITOR_H 42 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Mouse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_MOUSE_H 20 | #define EMSCRIPTEN_GLFW_MOUSE_H 21 | 22 | #include 23 | #include 24 | #include "Types.h" 25 | #include "Cursor.h" 26 | 27 | using glfw_mouse_button_state_t = int; // ex: GLFW_RELEASE 28 | using glfw_mouse_button_t = int; // ex: GLFW_MOUSE_BUTTON_LEFT 29 | using glfw_cursor_mode_t = int; // ex: GLFW_CURSOR_NORMAL 30 | 31 | namespace emscripten::glfw3 { 32 | 33 | class Window; 34 | 35 | 36 | class Mouse 37 | { 38 | public: 39 | 40 | constexpr bool isPointerLock() const { return fCursorMode == GLFW_CURSOR_DISABLED; } 41 | 42 | inline std::shared_ptr hideCursor() { 43 | fVisibleCursor = fCursor; 44 | fCursor = StandardCursor::getHiddenCursor(); 45 | return fCursor; 46 | } 47 | 48 | inline std::shared_ptr showCursor() { 49 | fCursor = fVisibleCursor; 50 | return fCursor; 51 | } 52 | 53 | inline bool isCursorHidden() const { return fCursor == StandardCursor::getHiddenCursor(); } 54 | 55 | friend class Window; 56 | 57 | public: 58 | glfw_mouse_button_state_t fLastButtonState{GLFW_RELEASE}; 59 | glfw_mouse_button_t fLastButton{-1}; 60 | std::array fButtonStates{GLFW_RELEASE}; 61 | 62 | glfw_cursor_mode_t fCursorMode{GLFW_CURSOR_NORMAL}; 63 | glfw_bool_t fStickyMouseButtons{GLFW_FALSE}; 64 | 65 | Vec2 fCursorPos{}; 66 | Vec2 fCursorPosBeforePointerLock{}; 67 | Vec2 fCursorLockResidual{}; 68 | 69 | GLFWmousebuttonfun fButtonCallback{}; 70 | GLFWscrollfun fScrollCallback{}; 71 | GLFWcursorenterfun fCursorEnterCallback{}; 72 | GLFWcursorposfun fCursorPosCallback{}; 73 | 74 | private: 75 | static constexpr glfw_mouse_button_state_t kStickyPress = 3; 76 | 77 | private: 78 | std::shared_ptr fCursor{StandardCursor::getDefault()}; 79 | std::shared_ptr fVisibleCursor{fCursor}; 80 | }; 81 | 82 | } 83 | 84 | #endif //EMSCRIPTEN_GLFW_MOUSE_H 85 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Object.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_OBJECT_H 20 | #define EMSCRIPTEN_GLFW_OBJECT_H 21 | 22 | namespace emscripten::glfw3 { 23 | 24 | template 25 | class Object 26 | { 27 | public: 28 | using opaque_ptr_t = G *; 29 | 30 | virtual ~Object() = default; 31 | inline opaque_ptr_t asOpaquePtr() { return reinterpret_cast(&fOpaquePtr); } 32 | // because G is opaque it is fine to return non const (nothing can happen) 33 | inline opaque_ptr_t asOpaquePtr() const { return const_cast(reinterpret_cast(&fOpaquePtr)); } 34 | private: 35 | struct OpaquePtr {}; 36 | OpaquePtr fOpaquePtr{}; 37 | }; 38 | 39 | } 40 | 41 | #endif //EMSCRIPTEN_GLFW_OBJECT_H 42 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_TYPES_H 20 | #define EMSCRIPTEN_GLFW_TYPES_H 21 | 22 | namespace emscripten::glfw3 { 23 | 24 | template 25 | struct Vec2 26 | { 27 | union 28 | { 29 | T x; 30 | T width; 31 | }; 32 | 33 | union 34 | { 35 | T y; 36 | T height; 37 | }; 38 | 39 | template 40 | constexpr Vec2 &operator=(Vec2 const &iOther) 41 | { 42 | x = static_cast(iOther); 43 | y = static_cast(iOther); 44 | return *this; 45 | } 46 | 47 | constexpr bool operator==(Vec2 const &rhs) const { return x == rhs.x && y == rhs.y; } 48 | constexpr bool operator!=(Vec2 const &rhs) const { return !(rhs == *this); } 49 | }; 50 | 51 | template 52 | static constexpr Vec2 operator+(const Vec2 &lhs, const Vec2 &rhs) { return {lhs.x + rhs.x, lhs.y + rhs.y}; } 53 | 54 | template 55 | static constexpr Vec2 operator-(const Vec2 &lhs, const Vec2 &rhs) { return {lhs.x - rhs.x, lhs.y - rhs.y}; } 56 | 57 | } 58 | 59 | #endif //EMSCRIPTEN_GLFW_TYPES_H 60 | -------------------------------------------------------------------------------- /src/cpp/emscripten/glfw3/Window.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_WINDOW_H 20 | #define EMSCRIPTEN_GLFW_WINDOW_H 21 | 22 | #include 23 | #include 24 | #include "Object.h" 25 | #include "Config.h" 26 | #include "Events.h" 27 | #include "Mouse.h" 28 | #include "Keyboard.h" 29 | #include "Types.h" 30 | #include "Cursor.h" 31 | #include 32 | #include 33 | #include 34 | 35 | namespace emscripten::glfw3 { 36 | 37 | class Context; 38 | 39 | class Window : public Object 40 | { 41 | public: 42 | struct FullscreenRequest 43 | { 44 | GLFWwindow *fWindow{}; 45 | bool fResizeCanvas{}; 46 | }; 47 | 48 | struct PointerLockRequest 49 | { 50 | GLFWwindow *fWindow{}; 51 | }; 52 | 53 | struct PointerUnlockRequest 54 | { 55 | GLFWwindow *fWindow{}; 56 | glfw_cursor_mode_t fCursorMode; 57 | }; 58 | 59 | public: 60 | inline char const *getCanvasSelector() const { return fConfig.fCanvasSelector.data(); } 61 | inline bool isHiDPIAware() const { return fConfig.fScaleFramebuffer == GLFW_TRUE || fConfig.fScaleToMonitor == GLFW_TRUE; } 62 | inline int getShouldClose() const { return fShouldClose; } 63 | inline void setShouldClose(int iShouldClose) { fShouldClose = iShouldClose; } 64 | 65 | inline int getWidth() const { return fSize.width; } 66 | inline int getHeight() const { return fSize.height; } 67 | inline void getSize(int* oWidth, int* oHeight) const 68 | { 69 | if(oWidth) *oWidth = getWidth(); 70 | if(oHeight) *oHeight = getHeight(); 71 | } 72 | inline void setSize(Vec2 const &iSize) { setCanvasSize(iSize); } 73 | void setSizeLimits(int iMinWidth, int iMinHeight, int iMaxWidth, int iMaxHeight); 74 | void setAspectRatio(int iNumerator, int iDenominator); 75 | inline int getFramebufferWidth() const { return fFramebufferSize.width; } 76 | inline int getFramebufferHeight() const { return fFramebufferSize.height; } 77 | inline void getFramebufferSize(int* oWidth, int* oHeight) const 78 | { 79 | if(oWidth) *oWidth = getFramebufferWidth(); 80 | if(oHeight) *oHeight = getFramebufferHeight(); 81 | } 82 | void getPosition(int *oX, int *oY) const; 83 | // inline bool isPointOutside(int x, int y) const { return x < 0 || x > fWidth || y < 0 || y > fHeight; } 84 | // inline bool isPointInside(int x, int y) const { return !isPointOutside(x, y); } 85 | 86 | constexpr char const *getTitle() const { return fTitle ? fTitle->c_str() : nullptr; } 87 | void setTitle(char const *iTitle) { fTitle = iTitle ? std::optional(iTitle): std::nullopt; } 88 | 89 | constexpr bool isFocused() const { return fFocused; } 90 | void changeFocus(bool isFocussed); 91 | inline void focus() { changeFocus(true); } 92 | inline void blur() { changeFocus(false); } 93 | 94 | constexpr bool isHovered() const { return fHovered; } 95 | 96 | constexpr bool isFullscreen() const { return fFullscreen; } 97 | 98 | constexpr bool isPointerLock() const { return fMouse.isPointerLock(); } 99 | 100 | inline GLFWwindowsizefun setSizeCallback(GLFWwindowsizefun iCallback) { return std::exchange(fSizeCallback, iCallback); } 101 | inline GLFWframebuffersizefun setFramebufferSizeCallback(GLFWframebuffersizefun iCallback) { return std::exchange(fFramebufferSizeCallback, iCallback); } 102 | inline GLFWwindowfocusfun setFocusCallback(GLFWwindowfocusfun iCallback) { return std::exchange(fFocusCallback, iCallback); } 103 | 104 | void getContentScale(float* iXScale, float* iYScale) const; 105 | inline GLFWwindowcontentscalefun setContentScaleCallback(GLFWwindowcontentscalefun iCallback) { return std::exchange(fContentScaleCallback, iCallback); } 106 | 107 | // events 108 | inline GLFWwindowposfun setPosCallback(GLFWwindowposfun iCallback) { return std::exchange(fPosCallback, iCallback); } 109 | inline GLFWcursorposfun setCursorPosCallback(GLFWcursorposfun iCallback) { return std::exchange(fMouse.fCursorPosCallback, iCallback); } 110 | inline GLFWmousebuttonfun setMouseButtonCallback(GLFWmousebuttonfun iCallback) { return std::exchange(fMouse.fButtonCallback, iCallback); } 111 | inline GLFWscrollfun setScrollCallback(GLFWscrollfun iCallback) { return std::exchange(fMouse.fScrollCallback, iCallback); } 112 | inline GLFWcursorenterfun setCursorEnterCallback(GLFWcursorenterfun iCallback) { return std::exchange(fMouse.fCursorEnterCallback, iCallback); } 113 | inline GLFWkeyfun setKeyCallback(GLFWkeyfun iCallback) { return fKeyboard.setKeyCallback(iCallback); } 114 | inline GLFWcharfun setCharCallback(GLFWcharfun iCallback) { return fKeyboard.setCharCallback(iCallback); } 115 | 116 | // mouse 117 | inline void getCursorPos(double *oXPos, double *oYPos) const { 118 | if(oXPos) {*oXPos = fMouse.fCursorPos.x; } 119 | if(oYPos) {*oYPos = fMouse.fCursorPos.y; } 120 | } 121 | glfw_mouse_button_state_t getMouseButtonState(glfw_mouse_button_t iButton); 122 | void setCursor(std::shared_ptr const &iCursor); 123 | 124 | // keyboard 125 | glfw_key_state_t getKeyState(glfw_key_t iKey) { return fKeyboard.getKeyState(iKey); } 126 | 127 | // user pointer 128 | inline void *getUserPointer() const { return fUserPointer; } 129 | inline void setUserPointer(void *iPointer) { fUserPointer = iPointer; } 130 | 131 | // input mode 132 | void setInputMode(int iMode, int iValue); 133 | int getInputMode(int iMode) const; 134 | 135 | // monitor scale 136 | void setMonitorScale(float iScale); 137 | 138 | // fullscreen 139 | FullscreenRequest requestFullscreen(bool iResizeCanvas); 140 | void onEnterFullscreen(std::optional> const &iScreenSize); 141 | bool onExitFullscreen(); 142 | 143 | // pointerLock 144 | void onPointerLock(); 145 | bool onPointerUnlock(std::optional iCursorMode); 146 | 147 | // opacity 148 | float getOpacity() const { return fOpacity; } 149 | void setOpacity(float iOpacity); 150 | 151 | // visibility 152 | inline bool isVisible() const { return fVisible; } 153 | void setVisibility(bool iVisible); 154 | 155 | // window attributes 156 | int getAttrib(int iAttrib); 157 | void setAttrib(int iAttrib, int iValue); 158 | 159 | // OpenGL 160 | bool createGLContext(); 161 | void makeGLContextCurrent() const; 162 | constexpr bool hasGLContext() const { return fWebGLContextHandle != 0; } 163 | 164 | // Canvas 165 | int makeCanvasResizable(std::string_view canvasResizeSelector, 166 | std::optional handleSelector = std::nullopt); 167 | int unmakeCanvasResizable(); 168 | 169 | Window(Context *iContext, Config iConfig, float iMonitorScale, char const *iTitle); 170 | ~Window() override; 171 | constexpr bool isDestroyed() const { return fDestroyed; } 172 | 173 | friend class Context; 174 | 175 | protected: 176 | void init(int iWidth, int iHeight); 177 | void destroy(); 178 | void handleResizeRequest(); 179 | void registerEventListeners() { addOrRemoveEventListeners(true); } 180 | bool onMouseButtonDown(int iGLFWButton); 181 | bool onMouseButtonUp(EmscriptenMouseEvent const *iEvent); 182 | bool onMouseButtonUp(int iGLFWButton); 183 | inline bool onKeyDown(Keyboard::Event const &iEvent, emscripten::glfw3::browser_key_fun_t const &iBrowserKeyCallback) { return fKeyboard.onKeyDown(asOpaquePtr(), iEvent, iBrowserKeyCallback); } 184 | inline bool onKeyUp(Keyboard::Event const &iEvent, emscripten::glfw3::browser_key_fun_t const &iBrowserKeyCallback) { return fKeyboard.onKeyUp(asOpaquePtr(), iEvent, iBrowserKeyCallback); } 185 | void handleSuperPlusKeys(Keyboard::SuperPlusKeyTimeout const &iTimeout) { if(fKeyboard.hasSuperPlusKeys()) fKeyboard.handleSuperPlusKeys(asOpaquePtr(), iTimeout); } 186 | bool onFocusChange(bool iFocus); 187 | void setCursorMode(glfw_cursor_mode_t iCursorMode); 188 | bool maybeRescale(std::function const &iAction); 189 | inline void setResizable(bool iResizable) { fConfig.fResizable = toGlfwBool(iResizable); } 190 | inline bool isResizable() const { return toCBool(fConfig.fResizable); } 191 | void setCanvasSize(Vec2 const &iSize); 192 | void onResizeRequest(Vec2 const &iSize) { fResizeRequest = iSize; } 193 | void resize(Vec2 const &iSize); 194 | Vec2 maybeApplySizeConstraints(Vec2 const &iSize) const; 195 | 196 | private: 197 | EventListener fOnMouseButtonDown{}; 198 | EventListener fOnMouseEnter{}; 199 | EventListener fOnMouseLeave{}; 200 | EventListener fOnMouseWheel{}; 201 | EventListener fOnFocusChange{}; 202 | EventListener fOnBlurChange{}; 203 | 204 | // touch 205 | private: 206 | EventListener fOnTouchStart{}; 207 | void setCursorPos(EmscriptenTouchPoint const *iTouchPoint); 208 | void onGlobalTouchStart(GLFWwindow *iOriginWindow, EmscriptenTouchPoint const *iTouchPoint); 209 | void onGlobalTouchMove(EmscriptenTouchPoint const *iTouchPoint); 210 | void onGlobalTouchEnd(EmscriptenTouchPoint const *iTouchPoint); 211 | 212 | private: 213 | void addOrRemoveEventListeners(bool iAdd); 214 | inline float getScale() const { return isHiDPIAware() ? fMonitorScale : 1.0f; } 215 | void setCursorPos(Vec2 const &iPos); 216 | void onGlobalMouseMove(EmscriptenMouseEvent const *iEvent); 217 | void computePos(bool iAdjustCursor); 218 | 219 | private: 220 | Context *fContext; 221 | Config fConfig; 222 | float fMonitorScale; 223 | bool fDestroyed{}; 224 | bool fHovered{}; 225 | bool fFocused{}; 226 | bool fVisible{true}; 227 | bool fFullscreen{}; 228 | bool fFocusOnMouse{true}; 229 | Vec2 fSize{}; 230 | Vec2 fPos{}; 231 | Vec2 fFramebufferSize{}; 232 | Vec2 fMinSize{GLFW_DONT_CARE, GLFW_DONT_CARE}; 233 | Vec2 fMaxSize{GLFW_DONT_CARE, GLFW_DONT_CARE}; 234 | int fAspectRatioNumerator{GLFW_DONT_CARE}; 235 | int fAspectRatioDenominator{GLFW_DONT_CARE}; 236 | std::optional> fResizeRequest{}; 237 | std::optional> fSizeBeforeFullscreen{}; 238 | std::optional fTitle{}; 239 | float fOpacity{1.0f}; 240 | int fShouldClose{}; // GLFW bool 241 | EMSCRIPTEN_WEBGL_CONTEXT_HANDLE fWebGLContextHandle{}; 242 | Mouse fMouse{}; 243 | Keyboard fKeyboard{}; 244 | void *fUserPointer{}; 245 | GLFWwindowcontentscalefun fContentScaleCallback{}; 246 | GLFWwindowposfun fPosCallback{}; 247 | GLFWwindowsizefun fSizeCallback{}; 248 | GLFWwindowfocusfun fFocusCallback{}; 249 | GLFWframebuffersizefun fFramebufferSizeCallback{}; 250 | }; 251 | 252 | } 253 | 254 | #endif //EMSCRIPTEN_GLFW_WINDOW_H -------------------------------------------------------------------------------- /test/demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(emscripten_glfw_demo LANGUAGES CXX) 4 | 5 | set(target "demo") 6 | 7 | # the linker flag -sMAX_WEBGL_VERSION=2 is used because due to Emscripten internals, 8 | # the GLFW_CONTEXT_VERSION_MAJOR is ignored. When using the port, the flag is 9 | # automatically set so there is no need to set it. 10 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sMAX_WEBGL_VERSION=2 -sNO_DISABLE_EXCEPTION_CATCHING -s ASSERTIONS=1 -s WASM=1 -s SAFE_HEAP=1") 11 | 12 | add_executable(${target} src/main.cpp src/Triangle.cpp) 13 | set_target_properties(${target} PROPERTIES OUTPUT_NAME "main") 14 | target_link_libraries(${target} PRIVATE glfw3) 15 | 16 | # Copy (and processes) shell.html 17 | set(SHELL_SRC "${CMAKE_CURRENT_LIST_DIR}/shell.html") 18 | set(SHELL_DST "${CMAKE_CURRENT_BINARY_DIR}/main.html") 19 | 20 | add_custom_command( 21 | OUTPUT ${SHELL_DST} 22 | COMMAND ${CMAKE_COMMAND} -D SRC=${SHELL_SRC} -D DST=${SHELL_DST} -P ${CMAKE_CURRENT_LIST_DIR}/CopyResource.cmake 23 | DEPENDS ${SHELL_SRC} 24 | ) 25 | add_custom_target(copy_shell_html DEPENDS ${SHELL_DST}) 26 | 27 | add_dependencies(${target} copy_shell_html) 28 | 29 | # To build using emcc: 30 | # mkdir /tmp/build 31 | # cp shell.html /tmp/build/index.html 32 | # emcc --use-port=../../port/emscripten-glfw3.py src/main.cpp src/Triangle.cpp -o /tmp/build/main.js -------------------------------------------------------------------------------- /test/demo/CopyResource.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | message(STATUS "Copying resource SRC=${SRC} | DST=${DST}") 4 | 5 | configure_file(${SRC} ${DST} @ONLY) 6 | -------------------------------------------------------------------------------- /test/demo/src/Triangle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #ifndef EMSCRIPTEN_GLFW_HELLO_TRIANGLE_H 20 | #define EMSCRIPTEN_GLFW_HELLO_TRIANGLE_H 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | struct GLFWwindow; 27 | 28 | class Triangle 29 | { 30 | public: 31 | static std::unique_ptr init(GLFWwindow *iWindow, 32 | char const *iName, 33 | char const *iResizeContainerSelector = nullptr, 34 | char const *iResizeHandleSelector = nullptr); 35 | ~Triangle(); 36 | 37 | inline GLFWwindow *getWindow() const { return fWindow; } 38 | bool render(); 39 | void updateValues(); 40 | void toggleShow(); 41 | void zoomIn(); 42 | void zoomOut(); 43 | void zoomReset(); 44 | void setOpacity(float iOpacity); 45 | void close(); 46 | void toggleHiDPIAware(); 47 | void toggleResizable(); 48 | void toggleSizeLimits(); 49 | void toggleAspectRatio(); 50 | void updateTitle(); 51 | void setClipboardString(); 52 | void setClickURL(std::string_view iURL) { fClickURL = iURL;} 53 | 54 | void setBgColor(GLfloat iRed, GLfloat iGreen, GLfloat iBlue, GLfloat iAlpha = 1.0f); 55 | bool shouldClose() const; 56 | constexpr char const *getName() const { return fName; }; 57 | void onKeyChange(int iKey, int scancode, int iAction, int iMods); 58 | void onMouseChange(int button, int action, int mods); 59 | void handleMouseEvents(); 60 | 61 | void registerCallbacks(); 62 | 63 | static void registerNoWindowCallbacks(); 64 | static void updateNoWindowValues(); 65 | 66 | private: 67 | Triangle(GLFWwindow *iWindow, 68 | char const *iName, 69 | char const *iResizeContainerSelector, 70 | char const *iResizeHandleSelector, 71 | GLuint iProgram, GLint iVertexPositionAttribLocation, GLuint iTriangleGeoVAO); 72 | 73 | struct MouseEvent 74 | { 75 | bool fClicked{}; 76 | }; 77 | 78 | private: 79 | GLFWwindow *fWindow; 80 | char const *fName; 81 | char const *fResizeContainerSelector; 82 | char const *fResizeHandleSelector; 83 | GLuint fProgram; 84 | GLint fVertexPositionAttribLocation; 85 | GLuint fTriangleGeoVAO; 86 | GLfloat fBgRed{0}; 87 | GLfloat fBgGreen{0}; 88 | GLfloat fBgBlue{0}; 89 | GLfloat fBgAlpha{1.0f}; 90 | int fCursor{}; 91 | bool fHasSizeLimits{}; 92 | bool fHasAspectRatio{}; 93 | std::optional fLMBEventThisFrame{}; 94 | std::string fClickURL{}; 95 | std::vector fCursors{}; 96 | }; 97 | 98 | 99 | #endif //EMSCRIPTEN_GLFW_HELLO_TRIANGLE_H -------------------------------------------------------------------------------- /test/demo/src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2023 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include "Triangle.h" 23 | #include 24 | 25 | /** 26 | * This program acts both as a demo and as a test of many of the features of the implementation. As a result it is 27 | * probably not a good starting point. Please check the `examples` folder for more concise examples. */ 28 | 29 | static void consoleErrorHandler(int iErrorCode, char const *iErrorMessage) 30 | { 31 | printf("glfwError: %d | %s\n", iErrorCode, iErrorMessage); 32 | } 33 | 34 | std::vector> kTriangles{}; 35 | 36 | void setHtmlValue(std::string_view iElementSelector, std::string_view iValue); 37 | 38 | struct Event 39 | { 40 | enum class Type : int 41 | { 42 | exit = 1, 43 | toggleShow = 2, 44 | zoomIn = 3, 45 | zoomOut = 4, 46 | zoomReset = 5, 47 | opacity = 6, 48 | close = 7, 49 | toggleHiDPIAware = 8, 50 | toggleResizable = 9, 51 | toggleSizeLimits = 10, 52 | toggleAspectRatio = 11, 53 | updateTitle = 12, 54 | }; 55 | 56 | Type fType; 57 | GLFWwindow *fWindow{}; 58 | float fData{}; 59 | }; 60 | 61 | //------------------------------------------------------------------------ 62 | // popEvent 63 | //------------------------------------------------------------------------ 64 | std::optional popEvent() 65 | { 66 | int type{}; 67 | GLFWwindow *w; 68 | float data; 69 | EM_ASM({ 70 | if(Module['hasEvents']()) { 71 | const event = Module['popEvent'](); 72 | setValue($0, event.type, 'i32'); 73 | setValue($1, event.canvas, '*'); 74 | setValue($2, event.data, 'float'); 75 | } 76 | }, &type, &w, &data); 77 | if(type > 0) 78 | return Event{static_cast(type), w, data}; 79 | else 80 | return std::nullopt; 81 | } 82 | 83 | //------------------------------------------------------------------------ 84 | // handleEvents 85 | //------------------------------------------------------------------------ 86 | bool handleEvents() 87 | { 88 | while(true) 89 | { 90 | auto event = popEvent(); 91 | if(!event) 92 | return true; 93 | 94 | auto triangle = event->fWindow ? 95 | *std::find_if(kTriangles.begin(), kTriangles.end(), [w = event->fWindow](auto const &triangle) { return triangle->getWindow() == w; }): 96 | nullptr; 97 | 98 | switch(event->fType) 99 | { 100 | case Event::Type::exit: return false; 101 | case Event::Type::toggleShow: if(triangle) triangle->toggleShow(); break; 102 | case Event::Type::zoomIn: if(triangle) triangle->zoomIn(); break; 103 | case Event::Type::zoomOut: if(triangle) triangle->zoomOut(); break; 104 | case Event::Type::zoomReset: if(triangle) triangle->zoomReset(); break; 105 | case Event::Type::opacity: if(triangle) triangle->setOpacity(event->fData / 100.0f); break; 106 | case Event::Type::close: if(triangle) triangle->close(); break; 107 | case Event::Type::toggleHiDPIAware: if(triangle) triangle->toggleHiDPIAware(); break; 108 | case Event::Type::toggleResizable: if(triangle) triangle->toggleResizable(); break; 109 | case Event::Type::toggleSizeLimits: if(triangle) triangle->toggleSizeLimits(); break; 110 | case Event::Type::toggleAspectRatio: if(triangle) triangle->toggleAspectRatio(); break; 111 | case Event::Type::updateTitle: if(triangle) triangle->updateTitle(); break; 112 | default: break; 113 | } 114 | } 115 | } 116 | 117 | //------------------------------------------------------------------------ 118 | // one iteration of the loop 119 | //------------------------------------------------------------------------ 120 | bool iter() 121 | { 122 | glfwPollEvents(); 123 | 124 | if(!handleEvents()) 125 | return false; 126 | 127 | for(auto it = kTriangles.begin(); it != kTriangles.end();) 128 | { 129 | if((*it)->shouldClose()) 130 | it = kTriangles.erase(it); 131 | else 132 | ++it; 133 | } 134 | 135 | for(auto &v: kTriangles) 136 | { 137 | if(!v->render()) 138 | return false; 139 | } 140 | 141 | for(auto &v: kTriangles) 142 | v->updateValues(); 143 | 144 | Triangle::updateNoWindowValues(); 145 | 146 | return !kTriangles.empty(); 147 | } 148 | 149 | //------------------------------------------------------------------------ 150 | // loop 151 | //------------------------------------------------------------------------ 152 | void loop() 153 | { 154 | if(!iter()) 155 | { 156 | kTriangles.clear(); 157 | glfwTerminate(); 158 | emscripten_cancel_main_loop(); 159 | } 160 | } 161 | 162 | //------------------------------------------------------------------------ 163 | // main 164 | //------------------------------------------------------------------------ 165 | int main() 166 | { 167 | glfwSetErrorCallback(consoleErrorHandler); 168 | 169 | glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_EMSCRIPTEN); 170 | 171 | if(!glfwInit()) 172 | return -1; 173 | 174 | printf("GLFW: %s | Platform: 0x%x\n", glfwGetVersionString(), glfwGetPlatform()); 175 | printf("emscripten: v%d.%d.%d\n", __EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__); 176 | setHtmlValue("#version", glfwGetVersionString()); 177 | setHtmlValue("#action-key", emscripten::glfw3::IsRuntimePlatformApple() ? "⌘" : "CTRL"); 178 | 179 | auto canvas1Enabled = static_cast(EM_ASM_INT( return Module['canvas1Enabled']; )); 180 | #ifndef EMSCRIPTEN_GLFW3_DISABLE_MULTI_WINDOW_SUPPORT 181 | auto canvas2Enabled = static_cast(EM_ASM_INT( return Module['canvas2Enabled']; )); 182 | #else 183 | auto canvas2Enabled = false; 184 | #endif 185 | 186 | GLFWwindow *window1{}; 187 | if(canvas1Enabled) 188 | { 189 | glfwDefaultWindowHints(); 190 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 191 | glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_TRUE); 192 | emscripten_glfw_set_next_window_canvas_selector("#canvas1"); 193 | window1 = glfwCreateWindow(300, 200, "hello world | canvas 1", nullptr, nullptr); 194 | if(!window1) 195 | { 196 | glfwTerminate(); 197 | return -1; 198 | } 199 | setHtmlValue("input.canvas1.opacity", std::to_string(static_cast(glfwGetWindowOpacity(window1) * 100.0f))); 200 | } 201 | 202 | GLFWwindow *window2{}; 203 | if(canvas2Enabled) 204 | { 205 | glfwDefaultWindowHints(); 206 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 207 | glfwWindowHint(GLFW_SCALE_FRAMEBUFFER, GLFW_FALSE); 208 | emscripten_glfw_set_next_window_canvas_selector("#canvas2"); 209 | window2 = glfwCreateWindow(300, 200, "hello world | canvas 2", nullptr, nullptr); 210 | if(!window2) 211 | { 212 | glfwTerminate(); 213 | return -1; 214 | } 215 | setHtmlValue("input.canvas2.opacity", std::to_string(static_cast(glfwGetWindowOpacity(window2) * 100.0f))); 216 | } 217 | 218 | if(window1) 219 | { 220 | std::shared_ptr window1Triangle = Triangle::init(window1, "canvas1"); 221 | if(!window1Triangle) 222 | { 223 | glfwTerminate(); 224 | return -1; 225 | } 226 | window1Triangle->setClickURL("https://github.com/pongasoft/emscripten-glfw"); 227 | window1Triangle->setBgColor(0.5f, 0.5f, 0.5f); 228 | kTriangles.emplace_back(window1Triangle); 229 | } 230 | 231 | if(window2) 232 | { 233 | std::shared_ptr window2Triangle = Triangle::init(window2, "canvas2", "#canvas2-container", "#canvas2-handle"); 234 | if(!window2Triangle) 235 | { 236 | glfwTerminate(); 237 | return -1; 238 | } 239 | window2Triangle->setClickURL("https://github.com/pongasoft"); 240 | window2Triangle->setBgColor(1.0f, 0, 0.5f); 241 | kTriangles.emplace_back(window2Triangle); 242 | } 243 | 244 | for(auto &v: kTriangles) 245 | v->registerCallbacks(); 246 | 247 | if(window1 && window2) 248 | glfwFocusWindow(window1); 249 | 250 | Triangle::registerNoWindowCallbacks(); 251 | 252 | // allow F12 to be handled by the browser 253 | emscripten::glfw3::AddBrowserKeyCallback([](GLFWwindow* window, int key, int scancode, int action, int mods) { 254 | return mods == 0 && action == GLFW_PRESS && key == GLFW_KEY_F12; 255 | }); 256 | 257 | emscripten_set_main_loop(loop, 0, false); 258 | } -------------------------------------------------------------------------------- /tools/create_local_port.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import shutil 4 | import os 5 | import pathlib 6 | 7 | # Retrieve arguments 8 | binary_dir = sys.argv[1] 9 | version = sys.argv[2] 10 | 11 | local_port_file = os.path.join(binary_dir, 'emscripten-glfw3-local.py') 12 | shutil.copy2('port/emscripten-glfw3.py', local_port_file) 13 | 14 | sha512_file = os.path.join(binary_dir, 'archive', f'emscripten-glfw3-{version}.zip.sha512') 15 | 16 | # Read sha512_file and extract the sum 17 | with open(sha512_file, 'r') as file: 18 | sha512_sum = file.read().split(' ')[0] 19 | 20 | # Open the file in read & write mode ('r+') 21 | with open(local_port_file, 'r+') as file: 22 | lines = file.readlines() 23 | file.seek(0) 24 | for line in lines: 25 | if 'SHA512 = ' in line: 26 | file.write(line.replace(line, f"SHA512 = '{sha512_sum}'\n")) 27 | elif 'TAG = ' in line: 28 | file.write(line.replace(line, f"TAG = '{version}'\n")) 29 | elif 'EXTERNAL_PORT = ' in line: 30 | zip_file = os.path.join(binary_dir, 'archive', f'emscripten-glfw3-{version}.zip') 31 | zip_url = pathlib.Path(zip_file).as_uri() 32 | file.write(f'# {line}') 33 | file.write(line.replace(line, f"EXTERNAL_PORT = '{zip_url}'\n")) 34 | else: 35 | file.write(line) 36 | file.truncate() 37 | 38 | print(f'Created local port {local_port_file}\n') 39 | -------------------------------------------------------------------------------- /version.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2024 pongasoft 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * 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, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | * 16 | * @author Yan Pujante 17 | */ 18 | #pragma once 19 | 20 | #ifndef EMSCRIPTEN_GLFW_VERSION_H 21 | #define EMSCRIPTEN_GLFW_VERSION_H 22 | 23 | // this version number is comparable and can be used in #if statements 24 | // #if EMSCRIPTEN_GLFW_VERSION >= 3'4'0'20240801 25 | // .... code that can be used past a certain release 26 | // #endif 27 | #define EMSCRIPTEN_GLFW_VERSION @emscripten-glfw_VERSION_AS_NUMBER@ 28 | 29 | #define EMSCRIPTEN_GLFW_FULL_VERSION_STR "@emscripten-glfw_VERSION@" 30 | 31 | #endif --------------------------------------------------------------------------------