├── .clang-format ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── ci └── bump-version.js ├── cmake └── NodeJS.cmake ├── dependencies └── game_overlay.node.txt ├── examples ├── example_interactivity_console.js ├── example_interactivity_window.js ├── example_with_autohide.js ├── example_with_relaunch.js ├── example_with_resize.js └── example_with_visibility.js ├── include ├── overlay_logging.h ├── overlay_paint_frame.h ├── overlay_paint_frame_js.h ├── sl_overlay_api.h ├── sl_overlay_window.h ├── sl_overlays.h ├── sl_overlays_settings.h ├── stdafx.h └── user_input_callback.h ├── index.test.js ├── npm ├── index.js ├── package.json └── typings.d.ts ├── package.json ├── readme.md ├── src ├── main.cpp ├── module.cpp ├── overlay_logging.cpp ├── overlay_paint_frame.cpp ├── overlay_paint_frame_js.cpp ├── sl_overlay_api.cpp ├── sl_overlay_window.cpp ├── sl_overlays.cpp ├── sl_overlays_settings.cpp ├── stdafx.cpp └── user_input_callback.cpp └── yarn.lock /.clang-format: -------------------------------------------------------------------------------- 1 | # Basic Formatting 2 | BasedOnStyle: LLVM 3 | TabWidth: 4 4 | UseTab: ForIndentation 5 | ColumnLimit: 200 6 | 7 | # Language 8 | Language: Cpp 9 | Standard: Cpp11 10 | 11 | # Indentation 12 | AccessModifierOffset: 0 13 | ConstructorInitializerIndentWidth: 4 14 | ContinuationIndentWidth: 4 15 | IndentCaseLabels: false 16 | #IndentPPDirectives: true 17 | IndentWidth: 4 18 | IndentWrappedFunctionNames: true 19 | NamespaceIndentation: All 20 | 21 | # Includes 22 | #IncludeBlocks: Regroup 23 | IncludeCategories: 24 | - Regex: '^<' 25 | Priority: 1 26 | - Regex: '^"' 27 | Priority: 2 28 | SortIncludes: true 29 | 30 | # Alignment 31 | AlignAfterOpenBracket: AlwaysBreak 32 | AlignConsecutiveAssignments: false 33 | AlignConsecutiveDeclarations: false 34 | AlignEscapedNewlines: Left 35 | AlignOperands: false 36 | AlignTrailingComments: true 37 | DerivePointerAlignment: false 38 | PointerAlignment: Left 39 | 40 | # Wrapping and Breaking 41 | AllowAllParametersOfDeclarationOnNextLine: false 42 | AllowShortBlocksOnASingleLine: false 43 | AllowShortCaseLabelsOnASingleLine: false 44 | AllowShortFunctionsOnASingleLine: Empty 45 | AllowShortIfStatementsOnASingleLine: false 46 | AllowShortLoopsOnASingleLine: false 47 | AlwaysBreakAfterReturnType: None 48 | AlwaysBreakBeforeMultilineStrings: false 49 | AlwaysBreakTemplateDeclarations: true 50 | BraceWrapping: 51 | AfterClass: true 52 | AfterControlStatement: false 53 | AfterEnum: true 54 | AfterControlStatement: true 55 | # AfterExternBlock: false 56 | AfterFunction: true 57 | AfterNamespace: true 58 | AfterStruct: true 59 | AfterUnion: true 60 | BeforeCatch: false 61 | BeforeElse: false 62 | SplitEmptyFunction: false 63 | SplitEmptyRecord: false 64 | SplitEmptyNamespace: false 65 | BinPackArguments: false 66 | BinPackParameters: false 67 | BreakBeforeBinaryOperators: None 68 | BreakBeforeBraces: Custom 69 | BreakBeforeTernaryOperators: true 70 | BreakConstructorInitializers: BeforeColon 71 | #BreakInheritanceList: BeforeColon 72 | BreakStringLiterals: false 73 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 74 | Cpp11BracedListStyle: true 75 | 76 | # Spaces 77 | SpaceAfterCStyleCast: false 78 | SpaceAfterTemplateKeyword: false 79 | SpaceBeforeAssignmentOperators: true 80 | #SpaceBeforeCpp11BracedList: false 81 | #SpaceBeforeCtorInitializerColon: true 82 | #SpaceBeforeInheritanceColon: true 83 | SpaceBeforeParens: ControlStatements 84 | #SpaceBeforeRangeBasedForLoopColon: true 85 | SpaceInEmptyParentheses: false 86 | SpacesBeforeTrailingComments: 1 87 | SpacesInAngles: false 88 | SpacesInCStyleCastParentheses: false 89 | SpacesInContainerLiterals: false 90 | SpacesInParentheses: false 91 | SpacesInSquareBrackets: false 92 | 93 | # Other 94 | CommentPragmas: '^(!FIXME!|!TODO!|ToDo:)' 95 | CompactNamespaces: false 96 | DisableFormat: false 97 | FixNamespaceComments: true 98 | #ForEachMacros: '' 99 | KeepEmptyLinesAtTheStartOfBlocks: true 100 | ReflowComments: false 101 | SortUsingDeclarations: true 102 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | concurrency: 4 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | branches: [ "master" ] 10 | tags: 11 | - '*' 12 | pull_request: 13 | branches: [ "master" ] 14 | 15 | env: 16 | PACKAGE_DIRECTORY: game_overlay 17 | PACKAGE_NAME: game-overlay 18 | BUILD_CONFIGURATION: RelWithDebInfo 19 | BUILD_DIRECTORY: "build" 20 | DISTRIBUTE_DIRECTORY: "distribute" 21 | RELEASE_BUCKET: "obs-studio-deployment" 22 | ELECTRON_VERSION: "v29.4.3" 23 | 24 | permissions: 25 | contents: read 26 | 27 | jobs: 28 | build: 29 | name: 'Build a package' 30 | runs-on: windows-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - name: Show GitHub context 34 | env: 35 | GITHUB_CONTEXT: ${{ toJson(github) }} 36 | run: echo "$GITHUB_CONTEXT" 37 | - name: Get the version 38 | id: get_version 39 | run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT 40 | shell: bash 41 | - name: Install Node.js 42 | uses: actions/setup-node@v3 43 | with: 44 | node-version: '16.x' 45 | - name: Install dependencies 46 | run: yarn install --immutable --immutable-cache --check-cache 47 | - name: Add MSBuild to PATH 48 | uses: microsoft/setup-msbuild@v1 49 | - name: Configure 50 | run: cmake -H"${{ github.workspace }}" -B"${{env.BUILD_DIRECTORY}}" -G"Visual Studio 17 2022" -A x64 -DNODEJS_VERSION="${{env.ELECTRON_VERSION}}" -DCMAKE_INSTALL_PREFIX="${{env.INSTALL_PACKAGE_PATH}}" 51 | env: 52 | INSTALL_PACKAGE_PATH: "${{env.BUILD_DIRECTORY}}/${{env.DISTRIBUTE_DIRECTORY}}/${{env.PACKAGE_DIRECTORY}}" 53 | - name: Build 54 | run: cmake --build "${{env.BUILD_DIRECTORY}}" --target install --config ${{env.BUILD_CONFIGURATION}} 55 | - name: Put version into package.json 56 | if: startsWith(github.ref, 'refs/tags/') 57 | run: node ci/bump-version.js "${{ steps.get_version.outputs.VERSION }}" "${{env.PACKAGE_PATH}}" 58 | env: 59 | PACKAGE_PATH: "${{env.BUILD_DIRECTORY}}/${{env.DISTRIBUTE_DIRECTORY}}/${{env.PACKAGE_DIRECTORY}}" 60 | - name: Cache build 61 | uses: actions/cache@v3 62 | with: 63 | path: ${{env.BUILD_DIRECTORY}} 64 | key: ${{ runner.os }}-build-${{ github.sha }} 65 | 66 | upload_debug_symbols: 67 | needs: build 68 | name: 'Upload debug symbols' 69 | runs-on: windows-latest 70 | if: startsWith(github.ref, 'refs/tags/') 71 | steps: 72 | - name: Get the version of aws cli 73 | run: aws --version 74 | shell: powershell 75 | - name: Install specific version of AWS CLI 76 | run: | 77 | $version = "2.13.29" 78 | $zipfile = "AWSCLIV2.zip" 79 | Invoke-WebRequest -OutFile $zipfile "https://awscli.amazonaws.com/AWSCLIV2-$version.msi" 80 | Start-Process msiexec.exe -Wait -ArgumentList "/i $zipfile /qn" 81 | rm $zipfile 82 | shell: pwsh 83 | - name: Get the version of aws cli after install 84 | run: aws --version 85 | shell: powershell 86 | - name: Get build from cache 87 | id: cache-check 88 | uses: actions/cache@v3 89 | with: 90 | path: ${{env.BUILD_DIRECTORY}} 91 | key: ${{ runner.os }}-build-${{ github.sha }} 92 | - name: Check cache 93 | if: steps.cache-check.outputs.cache-hit != 'true' 94 | run: exit 1 95 | - name: Fetch symsrv-scripts 96 | uses: actions/checkout@v3 97 | with: 98 | fetch-depth: 2 99 | repository: stream-labs/symsrv-scripts 100 | path: symsrv-scripts 101 | - name: Run symbol server scripts 102 | run: ./symsrv-scripts/main.bat "${{ github.workspace }}/symsrv-scripts" ".\main.ps1 -localSourceDir '${{ github.workspace }}' -repo_userId 'stream-labs' -repo_name '${{env.PACKAGE_NAME}}' -repo_branch '${{ github.sha }}'" 103 | env: 104 | AWS_SYMB_ACCESS_KEY_ID: ${{secrets.AWS_SYMB_ACCESS_KEY_ID}} 105 | AWS_SYMB_SECRET_ACCESS_KEY: ${{secrets.AWS_SYMB_SECRET_ACCESS_KEY}} 106 | shell: powershell 107 | 108 | upload_release_package: 109 | needs: build 110 | name: 'Upload release package' 111 | runs-on: windows-latest 112 | if: startsWith(github.ref, 'refs/tags/') 113 | steps: 114 | - name: Get the version 115 | id: get_version 116 | run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT 117 | shell: bash 118 | - name: Get build from cache 119 | id: cache-check 120 | uses: actions/cache@v3 121 | with: 122 | path: ${{env.BUILD_DIRECTORY}} 123 | key: ${{ runner.os }}-build-${{ github.sha }} 124 | - name: Check cache 125 | if: steps.cache-check.outputs.cache-hit != 'true' 126 | run: exit 1 127 | - name: Tar artifact for deployment 128 | run: tar -cvzf ${{env.TARGET_ARTIFACT}}.tar.gz -C ${{env.INSTALL_DISTRIBUTE_PATH}} ${{env.PACKAGE_DIRECTORY}} 129 | env: 130 | TARGET_ARTIFACT: ${{env.PACKAGE_NAME}}-${{ steps.get_version.outputs.VERSION }}-win64 131 | INSTALL_DISTRIBUTE_PATH: "${{env.BUILD_DIRECTORY}}/${{env.DISTRIBUTE_DIRECTORY}}" 132 | - name: Configure AWS credentials 133 | uses: aws-actions/configure-aws-credentials@v1 134 | with: 135 | aws-access-key-id: ${{secrets.AWS_RELEASE_ACCESS_KEY_ID}} 136 | aws-secret-access-key: ${{secrets.AWS_RELEASE_SECRET_ACCESS_KEY}} 137 | aws-region: us-west-2 138 | - name: Deploy 139 | run: aws s3 cp ${{env.TARGET_ARTIFACT}}.tar.gz s3://${{env.RELEASE_BUCKET}} --acl public-read 140 | env: 141 | TARGET_ARTIFACT: ${{env.PACKAGE_NAME}}-${{ steps.get_version.outputs.VERSION }}-win64 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | settings.cfg 3 | .vs/ 4 | pids 5 | logs 6 | results 7 | lib/binding 8 | *.tgz 9 | npm-debug.log 10 | node_modules/ 11 | .idea/ 12 | .vscode/ 13 | dist/ 14 | streamlabs_game_overlay*.tar.gz 15 | yarn-error.log 16 | game-overlay-v*z 17 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/binding 3 | build/ 4 | test 5 | examples/ 6 | *.tgz 7 | npm-debug.log 8 | .npmignore 9 | .gitignore 10 | .vscode 11 | dist/ 12 | streamlabs_game_overlay*.tar.gz 13 | yarn-error.log -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [0.0.4](https://github.com/stream-labs/streamlabs-overlay/compare/v0.0.1...v0.0.4) (2019-04-08) 6 | 7 | 8 | 9 | # 1.1.0 (2019-03-15) 10 | 11 | 12 | ### Bug Fixes 13 | 14 | * lost quiting flag for webview thread ([acdf714](https://github.com/stream-labs/streamlabs-overlay/commit/acdf714)) 15 | * remove test code ([eeb3be0](https://github.com/stream-labs/streamlabs-overlay/commit/eeb3be0)) 16 | * use napi_get_buffer_info to get first param for hwnd handling ([c3ea0b6](https://github.com/stream-labs/streamlabs-overlay/commit/c3ea0b6)) 17 | * **typings:** addHWND returns overlay ID ([1d1c9d1](https://github.com/stream-labs/streamlabs-overlay/commit/1d1c9d1)) 18 | * **typings:** setTransparency requires overlayId ([c37ea6b](https://github.com/stream-labs/streamlabs-overlay/commit/c37ea6b)) 19 | * **typings:** thread status enum ([c7229b8](https://github.com/stream-labs/streamlabs-overlay/commit/c7229b8)) 20 | 21 | 22 | ### Features 23 | 24 | * add set position to hwnd overlays ([8b5d72b](https://github.com/stream-labs/streamlabs-overlay/commit/8b5d72b)) 25 | * JS library friendliness ([a68e1a3](https://github.com/stream-labs/streamlabs-overlay/commit/a68e1a3)), closes [#1](https://github.com/stream-labs/streamlabs-overlay/issues/1) 26 | * new api - use HWND to create overlay ([5fff9be](https://github.com/stream-labs/streamlabs-overlay/commit/5fff9be)) 27 | * node module and private package ([f25630e](https://github.com/stream-labs/streamlabs-overlay/commit/f25630e)) 28 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(game_overlay) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake") 8 | 9 | SET(NODEJS_URL "https://artifacts.electronjs.org/headers/dist" CACHE STRING "Node.JS URL") 10 | SET(NODEJS_NAME "iojs" CACHE STRING "Node.JS Name") 11 | SET(NODEJS_VERSION "v29.4.3" CACHE STRING "Node.JS Version") 12 | 13 | include(NodeJS) 14 | 15 | nodejs_init() 16 | 17 | set(OVERLAY_SOURCES 18 | src/main.cpp 19 | src/module.cpp 20 | src/overlay_logging.cpp 21 | src/overlay_paint_frame_js.cpp 22 | src/overlay_paint_frame.cpp 23 | src/sl_overlay_api.cpp 24 | src/sl_overlay_window.cpp 25 | src/sl_overlays_settings.cpp 26 | src/sl_overlays.cpp 27 | src/stdafx.cpp 28 | src/user_input_callback.cpp ) 29 | 30 | SET(PROJECT_INCLUDE_PATHS ${NODEJS_INCLUDE_DIRS} "${CMAKE_SOURCE_DIR}/include/" ) 31 | 32 | # Include N-API wrappers 33 | execute_process(COMMAND node -p "require('node-addon-api').include" 34 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 35 | OUTPUT_VARIABLE NODE_ADDON_API_DIR 36 | ) 37 | string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) 38 | string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) 39 | 40 | list(APPEND PROJECT_INCLUDE_PATHS ${NODE_ADDON_API_DIR}) 41 | 42 | add_definitions(-DNAPI_VERSION=7) 43 | 44 | add_nodejs_module(${PROJECT_NAME} ${OVERLAY_SOURCES}) 45 | 46 | target_link_libraries(${PROJECT_NAME} ${PROJECT_LIBRARIES} d2d1.lib dwrite.lib Dwmapi.lib) 47 | 48 | target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_INCLUDE_PATHS}) 49 | target_compile_definitions(${PROJECT_NAME} PRIVATE BUILDING_NODE_EXTENSION) 50 | 51 | set(CompilerFlags 52 | CMAKE_CXX_FLAGS 53 | CMAKE_CXX_FLAGS_DEBUG 54 | CMAKE_CXX_FLAGS_RELEASE 55 | CMAKE_CXX_FLAGS_RELWITHDEBINFO 56 | CMAKE_C_FLAGS 57 | CMAKE_C_FLAGS_DEBUG 58 | CMAKE_C_FLAGS_RELEASE 59 | CMAKE_C_FLAGS_RELWITHDEBINFO 60 | ) 61 | foreach(CompilerFlag ${CompilerFlags}) 62 | string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") 63 | endforeach() 64 | 65 | target_compile_definitions(${PROJECT_NAME} PRIVATE -DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) 66 | 67 | include(FetchContent) 68 | 69 | # Compare current linked libs with prev 70 | FetchContent_Declare( 71 | deps_checker 72 | URL "https://raw.githubusercontent.com/stream-labs/obs-studio-node/staging/dependency_checker/check_dependencies.cmd" 73 | DOWNLOAD_NO_EXTRACT true 74 | ) 75 | FetchContent_MakeAvailable(deps_checker) 76 | 77 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 78 | COMMAND ${deps_checker_SOURCE_DIR}/check_dependencies.cmd $ ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} $ 79 | ) 80 | 81 | 82 | SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") 83 | 84 | # Print all linked libs 85 | get_target_property(LIST_OF_LIBRARIES ${PROJECT_NAME} LINK_LIBRARIES) 86 | message(STATUS "List of libraries to link with: " "${LIST_OF_LIBRARIES}") 87 | 88 | # Install 89 | install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX} OPTIONAL) 90 | 91 | install(FILES $ DESTINATION ${CMAKE_INSTALL_PREFIX}) 92 | 93 | install( 94 | FILES 95 | ${game_overlay_SOURCE_DIR}/npm/package.json 96 | ${game_overlay_SOURCE_DIR}/npm/index.js 97 | ${game_overlay_SOURCE_DIR}/npm/typings.d.ts 98 | DESTINATION 99 | ${CMAKE_INSTALL_PREFIX}) 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /ci/bump-version.js: -------------------------------------------------------------------------------- 1 | const sh = require('shelljs'); 2 | const colors = require('colors/safe'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | function log_info(msg) { 7 | sh.echo(colors.magenta(msg)); 8 | } 9 | 10 | function log_error(msg) { 11 | sh.echo(colors.red(`ERROR: ${msg}`)); 12 | } 13 | 14 | const newVersion = process.argv[2]; 15 | const packagePath = process.argv[3]; 16 | log_info('Script Arguments: ' + process.argv.splice(2)); 17 | 18 | try { 19 | const file = path.join(process.cwd(), packagePath, 'package.json'); 20 | const jsonData = fs.readFileSync(file); 21 | const root = JSON.parse(jsonData.toString()); 22 | const currentVersion = root['version']; 23 | 24 | log_info('Current version: ' + currentVersion); 25 | log_info('New version: ' + newVersion); 26 | 27 | root['version'] = newVersion; 28 | log_info('Bumping version number...'); 29 | fs.writeFileSync(file, JSON.stringify(root, null, 2)); 30 | } catch (error) { 31 | log_error(error); 32 | sh.exit(1); 33 | } -------------------------------------------------------------------------------- /cmake/NodeJS.cmake: -------------------------------------------------------------------------------- 1 | # Defaults for standard Node.js builds 2 | set(NODEJS_DEFAULT_URL https://nodejs.org/download/release) 3 | set(NODEJS_DEFAULT_VERSION installed) 4 | set(NODEJS_VERSION_FALLBACK latest) 5 | set(NODEJS_DEFAULT_NAME node) 6 | set(NODEJS_DEFAULT_CHECKSUM SHASUMS256.txt) 7 | set(NODEJS_DEFAULT_CHECKTYPE SHA256) 8 | 9 | include(CMakeParseArguments) 10 | 11 | # Find a path by walking upward from a base directory until the path is 12 | # found. Sets the variable ${PATH} to False if the path can't 13 | # be determined 14 | function(find_path_parent NAME BASE PATH) 15 | set(ROOT ${BASE}) 16 | set(${PATH} ${ROOT}/${NAME} PARENT_SCOPE) 17 | set(DRIVE "^[A-Za-z]?:?/$") 18 | while(NOT ROOT MATCHES ${DRIVE} AND NOT EXISTS ${ROOT}/${NAME}) 19 | get_filename_component(ROOT ${ROOT} DIRECTORY) 20 | set(${PATH} ${ROOT}/${NAME} PARENT_SCOPE) 21 | endwhile() 22 | if(ROOT MATCHES ${DRIVE}) 23 | set(${PATH} False PARENT_SCOPE) 24 | endif() 25 | endfunction() 26 | 27 | # Shortcut for finding standard node module locations 28 | macro(find_nodejs_module NAME BASE PATH) 29 | find_path_parent(node_modules/${NAME} ${BASE} ${PATH}) 30 | endmacro() 31 | 32 | # Download with a bit of nice output (without spewing progress) 33 | function(download_file URL) 34 | message(STATUS "Downloading: ${URL}") 35 | file(APPEND ${TEMP}/download.log "Downloading: ${URL}\n") 36 | file(APPEND ${TEMP}/download.log "----------------------------------------\n") 37 | file(DOWNLOAD 38 | ${URL} 39 | ${ARGN} 40 | LOG DOWNLOAD_LOG 41 | ) 42 | file(APPEND ${TEMP}/download.log ${DOWNLOAD_LOG}) 43 | file(APPEND ${TEMP}/download.log "----------------------------------------\n") 44 | endfunction() 45 | 46 | # Embedded win_delay_load_hook file so that this file can be copied 47 | # into projects directly (recommended practice) 48 | function(nodejs_generate_delayload_hook OUTPUT) 49 | file(WRITE ${OUTPUT} "") 50 | file(APPEND ${OUTPUT} "/*\n") 51 | file(APPEND ${OUTPUT} " * When this file is linked to a DLL, it sets up a delay-load hook that\n") 52 | file(APPEND ${OUTPUT} " * intervenes when the DLL is trying to load the main node binary\n") 53 | file(APPEND ${OUTPUT} " * dynamically. Instead of trying to locate the .exe file it'll just return\n") 54 | file(APPEND ${OUTPUT} " * a handle to the process image.\n") 55 | file(APPEND ${OUTPUT} " *\n") 56 | file(APPEND ${OUTPUT} " * This allows compiled addons to work when node.exe or iojs.exe is renamed.\n") 57 | file(APPEND ${OUTPUT} " */\n") 58 | file(APPEND ${OUTPUT} "\n") 59 | file(APPEND ${OUTPUT} "#ifdef _MSC_VER\n") 60 | file(APPEND ${OUTPUT} "\n") 61 | file(APPEND ${OUTPUT} "#ifndef DELAYIMP_INSECURE_WRITABLE_HOOKS\n") 62 | file(APPEND ${OUTPUT} "#define DELAYIMP_INSECURE_WRITABLE_HOOKS\n") 63 | file(APPEND ${OUTPUT} "#endif\n") 64 | file(APPEND ${OUTPUT} "\n") 65 | file(APPEND ${OUTPUT} "#ifndef WIN32_LEAN_AND_MEAN\n") 66 | file(APPEND ${OUTPUT} "#define WIN32_LEAN_AND_MEAN\n") 67 | file(APPEND ${OUTPUT} "#endif\n") 68 | file(APPEND ${OUTPUT} "\n") 69 | file(APPEND ${OUTPUT} "#include \n") 70 | file(APPEND ${OUTPUT} "#include \n") 71 | file(APPEND ${OUTPUT} "#include \n") 72 | file(APPEND ${OUTPUT} "#include \n") 73 | file(APPEND ${OUTPUT} "#include \n") 74 | file(APPEND ${OUTPUT} "\n") 75 | file(APPEND ${OUTPUT} "static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) {\n") 76 | file(APPEND ${OUTPUT} " if (event != dliNotePreLoadLibrary) return NULL;\n") 77 | file(APPEND ${OUTPUT} "\n") 78 | file(APPEND ${OUTPUT} " if (_stricmp(info->szDll, \"iojs.exe\") != 0 &&\n") 79 | file(APPEND ${OUTPUT} " _stricmp(info->szDll, \"node.exe\") != 0 &&\n") 80 | file(APPEND ${OUTPUT} " _stricmp(info->szDll, \"node.dll\") != 0)\n") 81 | file(APPEND ${OUTPUT} " return NULL;\n") 82 | file(APPEND ${OUTPUT} "\n") 83 | file(APPEND ${OUTPUT} " // Get a handle to the current process executable.\n") 84 | file(APPEND ${OUTPUT} " HMODULE processModule = GetModuleHandle(NULL);\n") 85 | file(APPEND ${OUTPUT} "\n") 86 | file(APPEND ${OUTPUT} " // Get the path to the executable.\n") 87 | file(APPEND ${OUTPUT} " TCHAR processPath[_MAX_PATH];\n") 88 | file(APPEND ${OUTPUT} " GetModuleFileName(processModule, processPath, _MAX_PATH);\n") 89 | file(APPEND ${OUTPUT} "\n") 90 | file(APPEND ${OUTPUT} " // Get the name of the current executable.\n") 91 | file(APPEND ${OUTPUT} " LPTSTR processName = PathFindFileName(processPath);\n") 92 | file(APPEND ${OUTPUT} "\n") 93 | file(APPEND ${OUTPUT} " // If the current process is node or iojs, then just return the proccess \n") 94 | file(APPEND ${OUTPUT} " // module.\n") 95 | file(APPEND ${OUTPUT} " if (_tcsicmp(processName, TEXT(\"node.exe\")) == 0 ||\n") 96 | file(APPEND ${OUTPUT} " _tcsicmp(processName, TEXT(\"iojs.exe\")) == 0) {\n") 97 | file(APPEND ${OUTPUT} " return (FARPROC) processModule;\n") 98 | file(APPEND ${OUTPUT} " }\n") 99 | file(APPEND ${OUTPUT} "\n") 100 | file(APPEND ${OUTPUT} " // If it is another process, attempt to load 'node.dll' from the same \n") 101 | file(APPEND ${OUTPUT} " // directory.\n") 102 | file(APPEND ${OUTPUT} " PathRemoveFileSpec(processPath);\n") 103 | file(APPEND ${OUTPUT} " PathAppend(processPath, TEXT(\"node.dll\"));\n") 104 | file(APPEND ${OUTPUT} "\n") 105 | file(APPEND ${OUTPUT} " HMODULE nodeDllModule = GetModuleHandle(processPath);\n") 106 | file(APPEND ${OUTPUT} " if(nodeDllModule != NULL) {\n") 107 | file(APPEND ${OUTPUT} " // This application has a node.dll in the same directory as the executable,\n") 108 | file(APPEND ${OUTPUT} " // use that.\n") 109 | file(APPEND ${OUTPUT} " return (FARPROC) nodeDllModule;\n") 110 | file(APPEND ${OUTPUT} " }\n") 111 | file(APPEND ${OUTPUT} "\n") 112 | file(APPEND ${OUTPUT} " // Fallback to the current executable, which must statically link to \n") 113 | file(APPEND ${OUTPUT} " // node.lib\n") 114 | file(APPEND ${OUTPUT} " return (FARPROC) processModule;\n") 115 | file(APPEND ${OUTPUT} "}\n") 116 | file(APPEND ${OUTPUT} "\n") 117 | file(APPEND ${OUTPUT} "PfnDliHook __pfnDliNotifyHook2 = load_exe_hook;\n") 118 | file(APPEND ${OUTPUT} "\n") 119 | file(APPEND ${OUTPUT} "#endif\n") 120 | endfunction() 121 | 122 | # Sets up a project to build Node.js native modules 123 | # - Downloads required dependencies and unpacks them to the build directory. 124 | # Internet access is required the first invocation but not after ( 125 | # provided the download is successful) 126 | # - Sets up several variables for building against the downloaded 127 | # dependencies 128 | # - Guarded to prevent multiple executions, so a single project hierarchy 129 | # will only call this once 130 | function(nodejs_init) 131 | # Prevents this function from executing more than once 132 | if(NODEJS_INIT) 133 | return() 134 | endif() 135 | 136 | # Regex patterns used by the init function for component extraction 137 | set(HEADERS_MATCH "^([A-Fa-f0-9]+)[ \\t]+(([^-]+)-(.+)-headers\\.tar\\.gz)$") 138 | set(LIB32_MATCH "(^[0-9A-Fa-f]+)[ \\t]+(win-x86/([^/]*\\.lib))$") 139 | set(LIB64_MATCH "(^[0-9A-Fa-f]+)[ \\t]+(win-x64/(.*\\.lib))$") 140 | 141 | # Parse function arguments 142 | cmake_parse_arguments(nodejs_init 143 | "" "URL;NAME;VERSION;CHECKSUM;CHECKTYPE" "" ${ARGN} 144 | ) 145 | 146 | # Allow the download URL to be overridden by command line argument 147 | # NODEJS_URL 148 | if(NODEJS_URL) 149 | set(URL ${NODEJS_URL}) 150 | else() 151 | # Use the argument if specified, falling back to the default 152 | set(URL ${NODEJS_DEFAULT_URL}) 153 | if(nodejs_init_URL) 154 | set(URL ${nodejs_init_URL}) 155 | endif() 156 | endif() 157 | 158 | # Allow name to be overridden by command line argument NODEJS_NAME 159 | if(NODEJS_NAME) 160 | set(NAME ${NODEJS_NAME}) 161 | else() 162 | # Use the argument if specified, falling back to the default 163 | set(NAME ${NODEJS_DEFAULT_NAME}) 164 | if(nodejs_init_NAME) 165 | set(NAME ${nodejs_init_NAME}) 166 | endif() 167 | endif() 168 | 169 | # Allow the checksum file to be overridden by command line argument 170 | # NODEJS_CHECKSUM 171 | if(NODEJS_CHECKSUM) 172 | set(CHECKSUM ${NODEJS_CHECKSUM}) 173 | else() 174 | # Use the argument if specified, falling back to the default 175 | set(CHECKSUM ${NODEJS_DEFAULT_CHECKSUM}) 176 | if(nodejs_init_CHECKSUM) 177 | set(CHECKSUM ${nodejs_init_CHECKSUM}) 178 | endif() 179 | endif() 180 | 181 | # Allow the checksum type to be overriden by the command line argument 182 | # NODEJS_CHECKTYPE 183 | if(NODEJS_CHECKTYPE) 184 | set(CHECKTYPE ${NODEJS_CHECKTYPE}) 185 | else() 186 | # Use the argument if specified, falling back to the default 187 | set(CHECKTYPE ${NODEJS_DEFAULT_CHECKTYPE}) 188 | if(nodejs_init_CHECKTYPE) 189 | set(CHECKTYPE ${nodejs_init_CHECKTYPE}) 190 | endif() 191 | endif() 192 | 193 | # Allow the version to be overridden by the command line argument 194 | # NODEJS_VERSION 195 | if(NODEJS_VERSION) 196 | set(VERSION ${NODEJS_VERSION}) 197 | else() 198 | # Use the argument if specified, falling back to the default 199 | set(VERSION ${NODEJS_DEFAULT_VERSION}) 200 | if(nodejs_init_VERSION) 201 | set(VERSION ${nodejs_init_VERSION}) 202 | endif() 203 | endif() 204 | 205 | # "installed" is a special version that tries to use the currently 206 | # installed version (determined by running node) 207 | set(NODEJS_INSTALLED False CACHE BOOL "Node.js install status" FORCE) 208 | if(VERSION STREQUAL "installed") 209 | if(NOT NAME STREQUAL ${NODEJS_DEFAULT_NAME}) 210 | message(FATAL_ERROR 211 | "'Installed' version identifier can only be used with" 212 | "the core Node.js library" 213 | ) 214 | endif() 215 | # Fall back to the "latest" version if node isn't installed 216 | set(VERSION ${NODEJS_VERSION_FALLBACK}) 217 | # This has all of the implications of why the binary is called nodejs in the first place 218 | # https://lists.debian.org/debian-devel-announce/2012/07/msg00002.html 219 | # However, with nvm/n, its nearly standard to have a proper 'node' binary now (since the 220 | # apt-based one is so out of date), so for now just assume that this rare binary conflict 221 | # case is the degenerate case. May need a more complicated solution later. 222 | find_program(NODEJS_BINARY NAMES node nodejs) 223 | if(NODEJS_BINARY) 224 | execute_process( 225 | COMMAND ${NODEJS_BINARY} --version 226 | RESULT_VARIABLE INSTALLED_VERSION_RESULT 227 | OUTPUT_VARIABLE INSTALLED_VERSION 228 | OUTPUT_STRIP_TRAILING_WHITESPACE 229 | ) 230 | if(INSTALLED_VERSION_RESULT STREQUAL "0") 231 | set(NODEJS_INSTALLED True CACHE BOOL 232 | "Node.js install status" FORCE 233 | ) 234 | set(VERSION ${INSTALLED_VERSION}) 235 | endif() 236 | endif() 237 | endif() 238 | 239 | # Create a temporary download directory 240 | set(TEMP ${CMAKE_CURRENT_BINARY_DIR}/temp) 241 | if(EXISTS ${TEMP}) 242 | file(REMOVE_RECURSE ${TEMP}) 243 | endif() 244 | file(MAKE_DIRECTORY ${TEMP}) 245 | 246 | # Unless the target is special version "latest", the parameters 247 | # necessary to construct the root path are known 248 | if(NOT VERSION STREQUAL "latest") 249 | set(ROOT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}/${VERSION}) 250 | # Extract checksums from the existing checksum file 251 | set(CHECKSUM_TARGET ${ROOT}/CHECKSUM) 252 | endif() 253 | 254 | # If we're trying to determine the version or we haven't saved the 255 | # checksum file for this version, download it from the specified server 256 | if(VERSION STREQUAL "latest" OR 257 | (DEFINED ROOT AND NOT EXISTS ${ROOT}/CHECKSUM)) 258 | if(DEFINED ROOT) 259 | # Clear away the old checksum in case the new one is different 260 | # and/or it fails to download 261 | file(REMOVE ${ROOT}/CHECKSUM) 262 | endif() 263 | file(REMOVE ${TEMP}/CHECKSUM) 264 | download_file( 265 | ${URL}/${VERSION}/${CHECKSUM} 266 | ${TEMP}/CHECKSUM 267 | INACTIVITY_TIMEOUT 10 268 | STATUS CHECKSUM_STATUS 269 | ) 270 | list(GET CHECKSUM_STATUS 0 CHECKSUM_STATUS) 271 | if(CHECKSUM_STATUS GREATER 0) 272 | file(REMOVE ${TEMP}/CHECKSUM) 273 | message(FATAL_ERROR 274 | "Unable to download checksum file" 275 | ) 276 | endif() 277 | # Extract checksums from the temporary file 278 | set(CHECKSUM_TARGET ${TEMP}/CHECKSUM) 279 | endif() 280 | 281 | # Extract the version, name, header archive and archive checksum 282 | # from the file. This first extract is what defines / specifies the 283 | # actual version number and name. 284 | file(STRINGS 285 | ${CHECKSUM_TARGET} HEADERS_CHECKSUMS 286 | REGEX ${HEADERS_MATCH} 287 | ) 288 | 289 | if(NOT HEADERS_CHECKSUMS) 290 | file(REMOVE ${TEMP}/CHECKSUM) 291 | if(DEFINED ROOT) 292 | file(REMOVE ${ROOT}/CHECKSUM) 293 | endif() 294 | message(FATAL_ERROR "Unable to extract header archive checksum") 295 | endif() 296 | 297 | foreach(HEADER ${HEADERS_CHECKSUMS}) 298 | message("Found headers archive: ${HEADER}") 299 | string(REGEX MATCH ${HEADERS_MATCH} HEADERS_CHECKSUM ${HEADER}) 300 | 301 | if (HEADERS_CHECKSUM AND CMAKE_MATCH_3 STREQUAL NAME) 302 | break() 303 | endif() 304 | 305 | unset(HEADERS_CHECKSUM) 306 | endforeach() 307 | 308 | if (NOT HEADERS_CHECKSUM) 309 | message(FATAL_ERROR "Failed to find matching headers for ${NAME}") 310 | endif() 311 | 312 | set(HEADERS_CHECKSUM ${CMAKE_MATCH_1}) 313 | set(NAME ${CMAKE_MATCH_3}) 314 | set(VERSION ${CMAKE_MATCH_4}) 315 | set(HEADERS_ARCHIVE ${CMAKE_MATCH_2}) 316 | # Make sure that the root directory exists, and that the checksum 317 | # file has been moved over from temp 318 | if(DEFINED ROOT) 319 | set(OLD_ROOT ${ROOT}) 320 | endif() 321 | set(ROOT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}/${VERSION}) 322 | if(DEFINED OLD_ROOT AND NOT ROOT STREQUAL "${OLD_ROOT}") 323 | file(REMOVE ${TEMP}/CHECKSUM) 324 | file(REMOVE ${ROOT}/CHECKSUM) 325 | message(FATAL_ERROR "Version/Name mismatch - ${ROOT} vs ${OLD_ROOT}") 326 | endif() 327 | file(MAKE_DIRECTORY ${ROOT}) 328 | if(EXISTS ${TEMP}/CHECKSUM) 329 | file(REMOVE ${ROOT}/CHECKSUM) 330 | file(RENAME ${TEMP}/CHECKSUM ${ROOT}/CHECKSUM) 331 | endif() 332 | 333 | # Now that its fully resolved, report the name and version of Node.js being 334 | # used 335 | message(STATUS "NodeJS: Using ${NAME}, version ${VERSION}") 336 | 337 | # Download the headers for the version being used 338 | # Theoretically, these could be found by searching the installed 339 | # system, but in practice, this can be error prone. They're provided 340 | # on the download servers, so just use the ones there. 341 | if(NOT EXISTS ${ROOT}/include) 342 | file(REMOVE ${TEMP}/${HEADERS_ARCHIVE}) 343 | download_file( 344 | ${URL}/${VERSION}/${HEADERS_ARCHIVE} 345 | ${TEMP}/${HEADERS_ARCHIVE} 346 | INACTIVITY_TIMEOUT 10 347 | EXPECTED_HASH ${CHECKTYPE}=${HEADERS_CHECKSUM} 348 | STATUS HEADERS_STATUS 349 | ) 350 | list(GET HEADERS_STATUS 0 HEADERS_STATUS) 351 | if(HEADER_STATUS GREATER 0) 352 | file(REMOVE ${TEMP}/${HEADERS_ARCHIVE}) 353 | message(FATAL_ERROR "Unable to download Node.js headers") 354 | endif() 355 | file(MAKE_DIRECTORY ${TEMP}/${VERSION}) 356 | execute_process( 357 | COMMAND ${CMAKE_COMMAND} -E tar xfz ${TEMP}/${HEADERS_ARCHIVE} 358 | WORKING_DIRECTORY ${TEMP}/${VERSION} 359 | ) 360 | 361 | # This adapts the header extraction to support a number of different 362 | # header archive contents in addition to the one used by the 363 | # default Node.js library 364 | unset(NODEJS_HEADERS_PATH CACHE) 365 | 366 | set(NODEJS_HEADERS_SEARCH_PATHS 367 | ${TEMP}/${VERSION}/node_headers 368 | ${TEMP}/${VERSION}/${NAME}-${VERSION}-headers 369 | ${TEMP}/${VERSION}/${NAME}-${VERSION} 370 | ${TEMP}/${VERSION}/${NODEJS_DEFAULT_NAME}-${VERSION}-headers 371 | ${TEMP}/${VERSION}/${NODEJS_DEFAULT_NAME}-${VERSION} 372 | ${TEMP}/${VERSION}/${NODEJS_DEFAULT_NAME} 373 | ${TEMP} 374 | ) 375 | 376 | foreach(HEADER_PATH ${NODEJS_HEADERS_SEARCH_PATHS}) 377 | message("Searching path for headers: ${HEADER_PATH}") 378 | 379 | find_path(NODEJS_HEADERS_PATH 380 | NAMES src include 381 | PATHS ${HEADER_PATH} 382 | NO_DEFAULT_PATH 383 | ) 384 | 385 | if (NODEJS_HEADERS_PATH) 386 | break() 387 | endif() 388 | endforeach() 389 | 390 | if(NOT NODEJS_HEADERS_PATH) 391 | message(FATAL_ERROR "Unable to find extracted headers folder") 392 | endif() 393 | 394 | # Move the headers into a standard location with a standard layout 395 | file(REMOVE ${TEMP}/${HEADERS_ARCHIVE}) 396 | file(REMOVE_RECURSE ${ROOT}/include) 397 | if(EXISTS ${NODEJS_HEADERS_PATH}/include/node) 398 | file(RENAME ${NODEJS_HEADERS_PATH}/include/node ${ROOT}/include) 399 | elseif(EXISTS ${NODEJS_HEADERS_PATH}/src) 400 | file(MAKE_DIRECTORY ${ROOT}/include) 401 | if(NOT EXISTS ${NODEJS_HEADERS_PATH}/src) 402 | file(REMOVE_RECURSE ${ROOT}/include) 403 | message(FATAL_ERROR "Unable to find core headers") 404 | endif() 405 | file(COPY ${NODEJS_HEADERS_PATH}/src/ 406 | DESTINATION ${ROOT}/include 407 | ) 408 | if(NOT EXISTS ${NODEJS_HEADERS_PATH}/deps/uv/include) 409 | file(REMOVE_RECURSE ${ROOT}/include) 410 | message(FATAL_ERROR "Unable to find libuv headers") 411 | endif() 412 | file(COPY ${NODEJS_HEADERS_PATH}/deps/uv/include/ 413 | DESTINATION ${ROOT}/include 414 | ) 415 | if(NOT EXISTS ${NODEJS_HEADERS_PATH}/deps/v8/include) 416 | file(REMOVE_RECURSE ${ROOT}/include) 417 | message(FATAL_ERROR "Unable to find v8 headers") 418 | endif() 419 | file(COPY ${NODEJS_HEADERS_PATH}/deps/v8/include/ 420 | DESTINATION ${ROOT}/include 421 | ) 422 | if(NOT EXISTS ${NODEJS_HEADERS_PATH}/deps/zlib) 423 | file(REMOVE_RECURSE ${ROOT}/include) 424 | message(FATAL_ERROR "Unable to find zlib headers") 425 | endif() 426 | file(COPY ${NODEJS_HEADERS_PATH}/deps/zlib/ 427 | DESTINATION ${ROOT}/include 428 | ) 429 | endif() 430 | file(REMOVE_RECURSE ${NODEJS_HEADERS_PATH}) 431 | unset(NODEJS_HEADERS_PATH CACHE) 432 | endif() 433 | 434 | # Only download the libraries on windows, since its the only place 435 | # its necessary. Note, this requires rerunning CMake if moving 436 | # a module from one platform to another (should happen automatically 437 | # with most generators) 438 | if(WIN32) 439 | # Download the win32 library for linking 440 | file(STRINGS 441 | ${ROOT}/CHECKSUM LIB32_CHECKSUM 442 | LIMIT_COUNT 1 443 | REGEX ${LIB32_MATCH} 444 | ) 445 | if(NOT LIB32_CHECKSUM) 446 | message(FATAL_ERROR "Unable to extract x86 library checksum") 447 | endif() 448 | string(REGEX MATCH ${LIB32_MATCH} LIB32_CHECKSUM ${LIB32_CHECKSUM}) 449 | set(LIB32_CHECKSUM ${CMAKE_MATCH_1}) 450 | set(LIB32_PATH win-x86) 451 | set(LIB32_NAME ${CMAKE_MATCH_3}) 452 | set(LIB32_TARGET ${CMAKE_MATCH_2}) 453 | if(NOT EXISTS ${ROOT}/${LIB32_PATH}) 454 | file(REMOVE_RECURSE ${TEMP}/${LIB32_PATH}) 455 | download_file( 456 | ${URL}/${VERSION}/${LIB32_TARGET} 457 | ${TEMP}/${LIB32_PATH}/${LIB32_NAME} 458 | INACTIVITY_TIMEOUT 10 459 | EXPECTED_HASH ${CHECKTYPE}=${LIB32_CHECKSUM} 460 | STATUS LIB32_STATUS 461 | ) 462 | list(GET LIB32_STATUS 0 LIB32_STATUS) 463 | if(LIB32_STATUS GREATER 0) 464 | message(FATAL_ERROR 465 | "Unable to download Node.js windows library (32-bit)" 466 | ) 467 | endif() 468 | file(REMOVE_RECURSE ${ROOT}/${LIB32_PATH}) 469 | file(MAKE_DIRECTORY ${ROOT}/${LIB32_PATH}) 470 | file(RENAME 471 | ${TEMP}/${LIB32_PATH}/${LIB32_NAME} 472 | ${ROOT}/${LIB32_PATH}/${LIB32_NAME} 473 | ) 474 | file(REMOVE_RECURSE ${TEMP}/${LIB32_PATH}) 475 | endif() 476 | 477 | # Download the win64 library for linking 478 | file(STRINGS 479 | ${ROOT}/CHECKSUM LIB64_CHECKSUM 480 | LIMIT_COUNT 1 481 | REGEX ${LIB64_MATCH} 482 | ) 483 | if(NOT LIB64_CHECKSUM) 484 | message(FATAL_ERROR "Unable to extract x64 library checksum") 485 | endif() 486 | string(REGEX MATCH ${LIB64_MATCH} LIB64_CHECKSUM ${LIB64_CHECKSUM}) 487 | set(LIB64_CHECKSUM ${CMAKE_MATCH_1}) 488 | set(LIB64_PATH win-x64) 489 | set(LIB64_NAME ${CMAKE_MATCH_3}) 490 | set(LIB64_TARGET ${CMAKE_MATCH_2}) 491 | if(NOT EXISTS ${ROOT}/${LIB64_PATH}) 492 | file(REMOVE_RECURSE ${TEMP}/${LIB64_PATH}) 493 | download_file( 494 | ${URL}/${VERSION}/${LIB64_TARGET} 495 | ${TEMP}/${LIB64_PATH}/${LIB64_NAME} 496 | INACTIVITY_TIMEOUT 10 497 | EXPECTED_HASH ${CHECKTYPE}=${LIB64_CHECKSUM} 498 | STATUS LIB64_STATUS 499 | ) 500 | list(GET LIB64_STATUS 0 LIB64_STATUS) 501 | if(LIB64_STATUS GREATER 0) 502 | message(FATAL_ERROR 503 | "Unable to download Node.js windows library (64-bit)" 504 | ) 505 | endif() 506 | file(REMOVE_RECURSE ${ROOT}/${LIB64_PATH}) 507 | file(MAKE_DIRECTORY ${ROOT}/${LIB64_PATH}) 508 | file(RENAME 509 | ${TEMP}/${LIB64_PATH}/${LIB64_NAME} 510 | ${ROOT}/${LIB64_PATH}/${LIB64_NAME} 511 | ) 512 | file(REMOVE_RECURSE ${TEMP}/${LIB64_PATH}) 513 | endif() 514 | endif() 515 | 516 | # The downloaded headers should always be set for inclusion 517 | list(APPEND INCLUDE_DIRS ${ROOT}/include) 518 | 519 | # Look for the NAN module, and add it to the includes 520 | find_nodejs_module( 521 | nan 522 | ${CMAKE_CURRENT_SOURCE_DIR} 523 | NODEJS_NAN_DIR 524 | ) 525 | if(NODEJS_NAN_DIR) 526 | list(APPEND INCLUDE_DIRS ${NODEJS_NAN_DIR}) 527 | endif() 528 | 529 | # Under windows, we need a bunch of libraries (due to the way 530 | # dynamic linking works) 531 | if(WIN32) 532 | # Generate and use a delay load hook to allow the node binary 533 | # name to be changed while still loading native modules 534 | set(DELAY_LOAD_HOOK ${CMAKE_CURRENT_BINARY_DIR}/win_delay_load_hook.c) 535 | nodejs_generate_delayload_hook(${DELAY_LOAD_HOOK}) 536 | set(SOURCES ${DELAY_LOAD_HOOK}) 537 | 538 | # Necessary flags to get delayload working correctly 539 | list(APPEND LINK_FLAGS 540 | "-IGNORE:4199" 541 | "-DELAYLOAD:iojs.exe" 542 | "-DELAYLOAD:node.exe" 543 | "-DELAYLOAD:node.dll" 544 | ) 545 | 546 | # Core system libraries used by node 547 | list(APPEND LIBRARIES 548 | kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib 549 | advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib 550 | odbc32.lib Shlwapi.lib DelayImp.lib 551 | ) 552 | 553 | # Also link to the node stub itself (downloaded above) 554 | if(CMAKE_CL_64) 555 | list(APPEND LIBRARIES ${ROOT}/${LIB64_PATH}/${LIB64_NAME}) 556 | else() 557 | list(APPEND LIBRARIES ${ROOT}/${LIB32_PATH}/${LIB32_NAME}) 558 | endif() 559 | else() 560 | # Non-windows platforms should use these flags 561 | list(APPEND DEFINITIONS _LARGEFILE_SOURCE _FILE_OFFSET_BITS=64) 562 | endif() 563 | 564 | # Special handling for OSX / clang to allow undefined symbols 565 | # Define is required by node on OSX 566 | if(APPLE) 567 | list(APPEND LINK_FLAGS "-undefined dynamic_lookup") 568 | list(APPEND DEFINITIONS _DARWIN_USE_64_BIT_INODE=1) 569 | endif() 570 | 571 | # Export all settings for use as arguments in the rest of the build 572 | set(NODEJS_VERSION ${VERSION} PARENT_SCOPE) 573 | set(NODEJS_SOURCES ${SOURCES} PARENT_SCOPE) 574 | set(NODEJS_INCLUDE_DIRS ${INCLUDE_DIRS} PARENT_SCOPE) 575 | set(NODEJS_LIBRARIES ${LIBRARIES} PARENT_SCOPE) 576 | set(NODEJS_LINK_FLAGS ${LINK_FLAGS} PARENT_SCOPE) 577 | set(NODEJS_DEFINITIONS ${DEFINITIONS} PARENT_SCOPE) 578 | 579 | # Prevents this function from executing more than once 580 | set(NODEJS_INIT TRUE PARENT_SCOPE) 581 | endfunction() 582 | 583 | # Helper function for defining a node module 584 | # After nodejs_init, all of the settings and dependencies necessary to do 585 | # this yourself are defined, but this helps make sure everything is configured 586 | # correctly. Feel free to use it as a model to do this by hand (or to 587 | # tweak this configuration if you need something custom). 588 | function(add_nodejs_module NAME) 589 | # Validate name parameter (must be a valid C identifier) 590 | string(MAKE_C_IDENTIFIER ${NAME} ${NAME}_SYMBOL_CHECK) 591 | if(NOT "${NAME}" STREQUAL "${${NAME}_SYMBOL_CHECK}") 592 | message(FATAL_ERROR 593 | "Module name must be a valid C identifier. " 594 | "Suggested alternative: '${${NAME}_SYMBOL_CHECK}'" 595 | ) 596 | endif() 597 | # Make sure node is initialized (variables set) before defining the module 598 | if(NOT NODEJS_INIT) 599 | message(FATAL_ERROR 600 | "Node.js has not been initialized. " 601 | "Call nodejs_init before adding any modules" 602 | ) 603 | endif() 604 | # In order to match node-gyp, we need to build into type specific folders 605 | # ncmake takes care of this, but be sure to set CMAKE_BUILD_TYPE yourself 606 | # if invoking CMake directly 607 | if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) 608 | message(FATAL_ERROR 609 | "Configuration type must be specified. " 610 | "Set CMAKE_BUILD_TYPE or use a different generator" 611 | ) 612 | endif() 613 | 614 | # A node module is a shared library 615 | add_library(${NAME} SHARED ${NODEJS_SOURCES} ${ARGN}) 616 | # Add compiler defines for the module 617 | # Two helpful ones: 618 | # MODULE_NAME must match the name of the build library, define that here 619 | # ${NAME}_BUILD is for symbol visibility under windows 620 | string(TOUPPER "${NAME}_BUILD" ${NAME}_BUILD_DEF) 621 | target_compile_definitions(${NAME} 622 | PRIVATE MODULE_NAME=${NAME} 623 | PRIVATE ${${NAME}_BUILD_DEF} 624 | PUBLIC ${NODEJS_DEFINITIONS} 625 | ) 626 | # This properly defines includes for the module 627 | target_include_directories(${NAME} PUBLIC ${NODEJS_INCLUDE_DIRS}) 628 | 629 | # Add link flags to the module 630 | target_link_libraries(${NAME} ${NODEJS_LIBRARIES}) 631 | 632 | # Set required properties for the module to build properly 633 | # Correct naming, symbol visiblity and C++ standard 634 | set_target_properties(${NAME} PROPERTIES 635 | OUTPUT_NAME ${NAME} 636 | PREFIX "" 637 | SUFFIX ".node" 638 | MACOSX_RPATH ON 639 | C_VISIBILITY_PRESET hidden 640 | CXX_VISIBILITY_PRESET hidden 641 | POSITION_INDEPENDENT_CODE TRUE 642 | CMAKE_CXX_STANDARD_REQUIRED TRUE 643 | CXX_STANDARD 17 644 | ) 645 | 646 | # Handle link flag cases properly 647 | # When there are link flags, they should be appended to LINK_FLAGS with space separation 648 | # If the list is emtpy (true for most *NIX platforms), this is a no-op 649 | foreach(NODEJS_LINK_FLAG IN LISTS NODEJS_LINK_FLAGS) 650 | set_property(TARGET ${NAME} APPEND_STRING PROPERTY LINK_FLAGS " ${NODEJS_LINK_FLAG}") 651 | endforeach() 652 | 653 | # Make sure we're buiilding in a build specific output directory 654 | # Only necessary on single-target generators (Make, Ninja) 655 | # Multi-target generators do this automatically 656 | # This (luckily) mirrors node-gyp conventions 657 | if(NOT CMAKE_CONFIGURATION_TYPES) 658 | set_property(TARGET ${NAME} PROPERTY 659 | LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BUILD_TYPE} 660 | ) 661 | endif() 662 | endfunction() 663 | -------------------------------------------------------------------------------- /dependencies/game_overlay.node.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/streamlabs/streamlabs-overlay/c212906721385a335f8093b20bbb83a28d4ea395/dependencies/game_overlay.node.txt -------------------------------------------------------------------------------- /examples/example_interactivity_console.js: -------------------------------------------------------------------------------- 1 | const streamlabs_overlays = require('../build/Release/game_overlay.node') 2 | 3 | console.log('Call start'); 4 | streamlabs_overlays.start(); 5 | console.log('status =' + streamlabs_overlays.getStatus() + ';'); 6 | 7 | function script_finished() { 8 | console.log('--------------- script_finished'); 9 | console.log(''); 10 | 11 | console.log('Call stop'); 12 | streamlabs_overlays.stop(); 13 | 14 | console.log('END'); 15 | } 16 | 17 | function step_finish() { 18 | console.log('--------------- step_finish'); 19 | console.log(''); 20 | 21 | 22 | setTimeout(script_finished, 2000); 23 | } 24 | 25 | function step_3() { 26 | console.log('--------------- step_3'); 27 | console.log(''); 28 | streamlabs_overlays.switchInteractiveMode(false); 29 | 30 | setTimeout(step_finish, 5000); 31 | } 32 | 33 | function step_2() { 34 | console.log('--------------- step_2'); 35 | console.log(''); 36 | streamlabs_overlays.switchInteractiveMode(true); 37 | 38 | setTimeout(step_3, 5000); 39 | } 40 | 41 | function step_1() { 42 | console.log('--------------- step_1'); 43 | console.log(''); 44 | console.log('Call to Interactive'); 45 | 46 | streamlabs_overlays.setMouseCallback((eventType, x, y, modifier) => { 47 | console.log('get MouseCallback: ' + eventType + ', ' + x + ', ' + y + ', ' + modifier); 48 | return 3; 49 | }); 50 | 51 | streamlabs_overlays.setKeyboardCallback((eventType, keyCode) => { 52 | console.log('get KeyboardCallback: ' + eventType + ', ' + keyCode); 53 | if (keyCode == 38) { 54 | streamlabs_overlays.switchInteractiveMode(false); 55 | } 56 | 57 | return 1; 58 | }); 59 | 60 | setTimeout(step_2, 5000); 61 | } 62 | 63 | setTimeout(step_1, 2000); 64 | 65 | setTimeout(script_finished, 25000); 66 | -------------------------------------------------------------------------------- /examples/example_interactivity_window.js: -------------------------------------------------------------------------------- 1 | 2 | const { app, BrowserWindow } = require("electron") 3 | const streamlabs_overlays = require('../build/Debug/game_overlay.node') 4 | const fs = require("fs") 5 | 6 | let win; 7 | // 8 | // # command to run this test 9 | // yarn run electron examples\example_interactivity_window.js 10 | // 11 | let test_started = false; 12 | 13 | function createWindow() { 14 | test_started = true; 15 | console.log('--------------- step_createWindow'); 16 | console.log(''); 17 | 18 | streamlabs_overlays.start(); 19 | 20 | win = new BrowserWindow({ 21 | show: false, 22 | width: 700, 23 | height: 700, 24 | frame: false, 25 | webPreferences: { 26 | offscreen: true, 27 | nodeIntegration: false, 28 | }, 29 | }) 30 | 31 | win.loadURL(`https://www.google.com/search?q=1`); 32 | //win.loadURL(`https://unixpapa.com/js/testkey.html`); //to test input 33 | 34 | let hwnd = win.getNativeWindowHandle(); 35 | console.log(hwnd); 36 | let overlayid = streamlabs_overlays.addHWND(hwnd); 37 | 38 | streamlabs_overlays.show(); 39 | streamlabs_overlays.setTransparency(overlayid, 100); 40 | streamlabs_overlays.setPosition(overlayid, 100, 100, 700, 700); 41 | 42 | win.webContents.on('paint', (event, dirty, image) => { 43 | streamlabs_overlays.paintOverlay(overlayid, image.getSize().width, image.getSize().height, image.getBitmap()); 44 | 45 | }) 46 | 47 | win.webContents.setFrameRate(10); 48 | win.webContents.invalidate(); 49 | win.webContents.once('dom-ready', () => { 50 | win.focus(); 51 | //win.webContents.sendInputEvent({ type: 'char', keyCode: '\u0022' }); 52 | //win.webContents.sendInputEvent({ type: "keyDown", keyCode: '\u0022' }); 53 | //win.webContents.sendInputEvent({ type: "keyUp", keyCode: '\u0022' }); 54 | //win.webContents.invalidate(); 55 | }); 56 | 57 | streamlabs_overlays.setMouseCallback((eventType, x, y, modifier) => { 58 | console.log('get first MouseCallback: ' + eventType + ', ' + x + ', ' + y + ', ' + modifier); 59 | return 1; 60 | }); 61 | 62 | streamlabs_overlays.setKeyboardCallback((eventType, keyCodeValue) => { 63 | //console.log('get KeyboardCallback: '+ eventType +', '+ keyCodeValue + ', '+ BrowserWindow.getAllWindows[0].getNativeWindowHandle()); 64 | console.log('get KeyboardCallback: ' + eventType + ', ' + keyCodeValue); 65 | //console.log('get KeyboardCallback: app ' + app.getPath("test") ); 66 | 67 | //BrowserWindow.getAllWindows()[0].webContents.sendInputEvent({type:eventType, x:10, y:10, keyCode:keyCodeValue}); 68 | try { 69 | win.webContents.sendInputEvent({ type: eventType, keyCode: keyCodeValue }); 70 | //win.webContents.invalidate(); 71 | //win.loadURL(`https://www.google.com/search?q=`+keyCodeValue); 72 | //let hwnd = win.getNativeWindowHandle(); 73 | //console.log(hwnd); 74 | } catch (error) { 75 | console.log(error); 76 | } 77 | 78 | if (keyCodeValue == 38) { 79 | streamlabs_overlays.switchInteractiveMode(false); 80 | } else { 81 | 82 | // win.focusOnWebView(); 83 | // win.webContents.sendInputEvent({type:eventType, keyCode:keyCodeValue}); 84 | // //win.loadURL(`https://www.google.com/search?q=2`) 85 | // win.webContents.invalidate(); 86 | } 87 | // win.focus(); 88 | //win.webContents.sendInputEvent({type:'keyDown', x:10, y:10, keyCode:'A'}); 89 | //win.webContents.sendInputEvent({type:'contextMenu', x:10, y:10}); 90 | //win.webContents.sendInputEvent({type:'char', x:10, y:10, keyCode:'A'}); 91 | return 2; 92 | }); 93 | 94 | 95 | win.on("closed", () => { win = null }); 96 | 97 | setTimeout(step_1, 5000); 98 | } 99 | 100 | app.on("ready", createWindow); 101 | 102 | app.on("window-all-closed", () => { 103 | app.quit(); 104 | }) 105 | 106 | app.on("activate", () => { 107 | if (!test_started) { 108 | createWindow(); 109 | } 110 | }) 111 | 112 | function step_2() { 113 | console.log('--------------- step_2'); 114 | console.log(''); 115 | 116 | streamlabs_overlays.switchInteractiveMode(false); 117 | setTimeout(step_finish, 5000); 118 | } 119 | 120 | function step_finish() { 121 | streamlabs_overlays.stop(); 122 | app.quit(); 123 | } 124 | 125 | function step_1() { 126 | console.log('--------------- step_1'); 127 | console.log(''); 128 | console.log('Call to Interactive'); 129 | 130 | streamlabs_overlays.switchInteractiveMode(true); 131 | 132 | setTimeout(step_2, 10000); 133 | } 134 | -------------------------------------------------------------------------------- /examples/example_with_autohide.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require("electron") 2 | const streamlabs_overlays = require('../build/Release/game-overlay.node') 3 | const fs = require("fs") 4 | 5 | // 6 | // # command to run this test 7 | // yarn run electron examples\example_with_offscreen.js 8 | // 9 | let test_started = false; 10 | let win1; 11 | let overlayid1; 12 | let win2; 13 | let overlayid2; 14 | let hwnd; 15 | 16 | const frame_rate = 2; 17 | const win_width = 600; 18 | const win_height = 600; 19 | 20 | function createWindow() { 21 | test_started = true; 22 | console.log('--------------- step_createWindow'); 23 | console.log(''); 24 | 25 | streamlabs_overlays.start(); 26 | 27 | win1 = new BrowserWindow({ 28 | show: false, 29 | width: win_width-50, 30 | height: win_height-50, 31 | frame: false, 32 | webPreferences: { 33 | offscreen: true, 34 | nodeIntegration: false, 35 | }, 36 | }) 37 | 38 | win1.loadURL(`https://www.google.com/search?source=hp&q=test+time+out+&oq=test+time+out+`); 39 | 40 | hwnd = win1.getNativeWindowHandle(); 41 | console.log(hwnd); 42 | overlayid1 = streamlabs_overlays.addHWND(hwnd); 43 | streamlabs_overlays.setPosition(overlayid1, 100, 100, win_width-50, win_height-50); 44 | 45 | win1.webContents.on('paint', (event, dirty, image) => { 46 | if( streamlabs_overlays.paintOverlay(overlayid1, image.getSize().width, image.getSize().height, image.getBitmap() ) ===0 ) 47 | { 48 | win1.webContents.invalidate(); 49 | } 50 | }) 51 | 52 | win1.webContents.setFrameRate(frame_rate); 53 | win1.webContents.invalidate(); 54 | 55 | 56 | win2 = new BrowserWindow({ 57 | show: false, 58 | width: win_width, 59 | height: win_height, 60 | frame: false, 61 | webPreferences: { 62 | offscreen: true, 63 | nodeIntegration: false, 64 | }, 65 | }) 66 | 67 | win2.loadURL(`https://time.is/`); 68 | 69 | hwnd = win2.getNativeWindowHandle(); 70 | console.log(hwnd); 71 | overlayid2 = streamlabs_overlays.addHWND(hwnd); 72 | streamlabs_overlays.setPosition(overlayid2, 750,100, win_width, win_height); 73 | 74 | win2.webContents.on('paint', (event, dirty, image) => { 75 | if( streamlabs_overlays.paintOverlay(overlayid2, image.getSize().width, image.getSize().height, image.getBitmap()) === 0 ) { 76 | win2.webContents.invalidate(); 77 | } 78 | }) 79 | 80 | win2.webContents.setFrameRate(frame_rate); 81 | win2.webContents.invalidate(); 82 | 83 | streamlabs_overlays.show(); 84 | 85 | streamlabs_overlays.setAutohide(overlayid1, 5, 0); 86 | streamlabs_overlays.setAutohide(overlayid2, 5, 0); 87 | 88 | 89 | win1.on("closed", () => { win1 = null }); 90 | win2.on("closed", () => { win2 = null }); 91 | 92 | setTimeout(step_1, 10000); 93 | } 94 | 95 | app.on("ready", createWindow); 96 | 97 | app.on("window-all-closed", () => { 98 | app.quit(); 99 | }) 100 | 101 | app.on("activate", () => { 102 | if (!test_started) { 103 | createWindow(); 104 | } 105 | }) 106 | 107 | function step_1() { 108 | console.log('--------------- step_1'); 109 | console.log(''); 110 | 111 | console.log('Call get overlays ids'); 112 | var overlays_ids = streamlabs_overlays.getIds(); 113 | console.log(overlays_ids); 114 | 115 | streamlabs_overlays.setAutohide(overlayid1, 3, 50 ); 116 | streamlabs_overlays.setAutohide(overlayid2, 3, 50 ); 117 | 118 | //win1.webContents.invalidate(); 119 | //win2.webContents.invalidate(); 120 | 121 | 122 | setTimeout(step_2, 11000); 123 | } 124 | 125 | function step_2() { 126 | console.log('--------------- step_2'); 127 | console.log(''); 128 | 129 | console.log('Call get overlays ids'); 130 | var overlays_ids = streamlabs_overlays.getIds(); 131 | console.log(overlays_ids); 132 | 133 | win1.webContents.invalidate(); 134 | win2.webContents.invalidate(); 135 | 136 | setTimeout(step_finish, 11000); 137 | } 138 | 139 | function step_finish() { 140 | streamlabs_overlays.stop(); 141 | app.quit(); 142 | } -------------------------------------------------------------------------------- /examples/example_with_relaunch.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require("electron") 2 | const streamlabs_overlays = require('../build/Debug/game_overlay.node') 3 | const fs = require("fs") 4 | 5 | // 6 | // # command to run this test 7 | // yarn run electron examples\example_with_offscreen.js 8 | // 9 | let test_started = false; 10 | let win1; 11 | let overlayid1; 12 | let hwnd; 13 | 14 | const frame_rate = 2; 15 | const win_width = 600; 16 | const win_height = 600; 17 | 18 | function createWindow() { 19 | test_started = true; 20 | console.log('--------------- step_createWindow'); 21 | console.log(''); 22 | 23 | streamlabs_overlays.start("c:\\work\\over_log.log"); 24 | 25 | win1 = new BrowserWindow({ 26 | show: false, 27 | width: win_width-50, 28 | height: win_height-50, 29 | frame: false, 30 | webPreferences: { 31 | offscreen: true, 32 | nodeIntegration: false, 33 | }, 34 | }) 35 | 36 | win1.loadURL(`https://www.google.com/search?source=hp&q=test+time+out+&oq=test+time+out+`); 37 | 38 | hwnd = win1.getNativeWindowHandle(); 39 | console.log(hwnd); 40 | overlayid1 = streamlabs_overlays.addHWND(hwnd); 41 | streamlabs_overlays.setPosition(overlayid1, 100, 100, win_width-50, win_height-50); 42 | 43 | win1.webContents.on('paint', (event, dirty, image) => { 44 | if( streamlabs_overlays.paintOverlay(overlayid1, image.getSize().width, image.getSize().height, image.getBitmap() ) ===0 ) 45 | { 46 | win1.webContents.invalidate(); 47 | } 48 | }) 49 | 50 | win1.webContents.setFrameRate(frame_rate); 51 | win1.webContents.invalidate(); 52 | 53 | streamlabs_overlays.show(); 54 | 55 | streamlabs_overlays.setAutohide(overlayid1, 5, 0); 56 | 57 | 58 | win1.on("closed", () => { win1 = null }); 59 | 60 | setTimeout(step_1, 10000); 61 | } 62 | 63 | app.on("ready", createWindow); 64 | 65 | app.on("window-all-closed", () => { 66 | app.quit(); 67 | }) 68 | 69 | app.on("activate", () => { 70 | if (!test_started) { 71 | createWindow(); 72 | } 73 | }) 74 | 75 | function step_1() { 76 | console.log('--------------- step_1'); 77 | console.log(''); 78 | streamlabs_overlays.stop(); 79 | 80 | setTimeout(step_2, 1000); 81 | } 82 | 83 | function step_2() { 84 | console.log('--------------- step_2'); 85 | console.log(''); 86 | streamlabs_overlays.start("c:\\work\\over_log.log"); 87 | 88 | setTimeout(step_finish, 5000); 89 | console.log('--------------- step_2 finished'); 90 | } 91 | 92 | function step_finish() { 93 | console.log('--------------- step_finish'); 94 | streamlabs_overlays.stop(); 95 | app.quit(); 96 | } -------------------------------------------------------------------------------- /examples/example_with_resize.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require("electron") 2 | const electron = require('electron') 3 | const streamlabs_overlays = require('../build/Debug/game_overlay.node') 4 | const fs = require("fs") 5 | 6 | // 7 | // # command to run this test 8 | // yarn run electron examples\example_with_offscreen.js 9 | // 10 | let test_started = false; 11 | let win1; 12 | let overlayid1; 13 | let win2; 14 | let overlayid2; 15 | let win3; 16 | let overlayid3; 17 | let hwnd; 18 | 19 | const frame_rate = 2; 20 | const win_width = 700; 21 | const win_height = 700; 22 | 23 | function createWindow() { 24 | test_started = true; 25 | console.log('--------------- step_createWindow'); 26 | console.log(''); 27 | 28 | streamlabs_overlays.start(); 29 | 30 | win1 = new BrowserWindow({ 31 | show: false, 32 | width: win_width/2, 33 | height: win_height, 34 | frame: false, 35 | webPreferences: { 36 | offscreen: true, 37 | nodeIntegration: false, 38 | }, 39 | }) 40 | 41 | win1.loadURL(`https://codepen.io/jasonleewilson/pen/gPrxwX`); 42 | 43 | win1.setBounds({width: win_width/2, height: win_height, x: 100, y: 100 }); 44 | hwnd = win1.getNativeWindowHandle(); 45 | console.log(hwnd); 46 | 47 | overlayid1 = streamlabs_overlays.addHWND(hwnd); 48 | let win1_rect = win1.getBounds(); 49 | streamlabs_overlays.setPosition(overlayid1, win1_rect.x, win1_rect.y, Number(win1_rect.width), Number(win1_rect.height)); 50 | 51 | win1.webContents.on('paint', (event, dirty, image) => { 52 | streamlabs_overlays.paintOverlay(overlayid1, image.getSize().width, image.getSize().height, image.getBitmap()); 53 | }) 54 | 55 | win1.webContents.setFrameRate(frame_rate); 56 | win1.webContents.invalidate(); 57 | 58 | win2 = new BrowserWindow({ 59 | show: false, 60 | width: win_width, 61 | height: win_height/2, 62 | frame: false, 63 | webPreferences: { 64 | offscreen: true, 65 | nodeIntegration: false, 66 | }, 67 | }) 68 | 69 | win2.loadURL(`https://time.is/`); 70 | 71 | win2.setBounds({width: win_width, height: win_height/2, x: 100+win_width/2, y: 100 }); 72 | hwnd = win2.getNativeWindowHandle(); 73 | console.log(hwnd); 74 | overlayid2 = streamlabs_overlays.addHWND(hwnd); 75 | 76 | let win2_rect = win2.getBounds(); 77 | streamlabs_overlays.setPosition(overlayid2, win2_rect.x, win2_rect.y, Number(win2_rect.width), Number(win2_rect.height)); 78 | 79 | win2.webContents.on('paint', (event, dirty, image) => { 80 | streamlabs_overlays.paintOverlay(overlayid2, image.getSize().width, image.getSize().height, image.getBitmap()); 81 | 82 | }) 83 | 84 | win2.webContents.setFrameRate(frame_rate); 85 | win2.webContents.invalidate(); 86 | 87 | win3 = new BrowserWindow({ 88 | show: false, 89 | width: win_width, 90 | height: win_height/2, 91 | frame: false, 92 | webPreferences: { 93 | offscreen: true, 94 | nodeIntegration: false, 95 | }, 96 | }) 97 | 98 | win3.loadURL(`https://time.is/`); 99 | win3.setBounds({width: win_width, height: win_height/2, x: 100+win_width/2, y: 100 +win_height/2}); 100 | hwnd = win3.getNativeWindowHandle(); 101 | console.log(hwnd); 102 | overlayid3 = streamlabs_overlays.addHWND(hwnd); 103 | 104 | let win3_rect = win3.getBounds(); 105 | streamlabs_overlays.setPosition(overlayid3, win3_rect.x, win3_rect.y, Number(win3_rect.width), Number(win3_rect.height)); 106 | 107 | win3.webContents.on('paint', (event, dirty, image) => { 108 | streamlabs_overlays.paintOverlay(overlayid3, image.getSize().width, image.getSize().height, image.getBitmap()); 109 | }) 110 | 111 | win3.webContents.setFrameRate(frame_rate); 112 | win3.webContents.invalidate(); 113 | 114 | streamlabs_overlays.show(); 115 | 116 | win1.on("closed", () => { win1 = null }); 117 | win2.on("closed", () => { win2 = null }); 118 | win3.on("closed", () => { win3 = null }); 119 | 120 | setTimeout(step_1, 10000); 121 | } 122 | 123 | app.on("ready", createWindow); 124 | 125 | app.on("window-all-closed", () => { 126 | app.quit(); 127 | }) 128 | 129 | app.on("activate", () => { 130 | if (!test_started) { 131 | createWindow(); 132 | } 133 | }) 134 | 135 | function step_1() { 136 | console.log('--------------- step_1'); 137 | console.log(''); 138 | 139 | console.log('Call get overlays ids'); 140 | var overlays_ids = streamlabs_overlays.getIds(); 141 | console.log(overlays_ids); 142 | 143 | console.log('' + streamlabs_overlays.getInfo(overlays_ids[0]) ); 144 | 145 | console.log('Call set overlay transparency 20/255'); 146 | streamlabs_overlays.setTransparency(overlays_ids[0], 150); 147 | streamlabs_overlays.setTransparency(overlays_ids[1], 150); 148 | streamlabs_overlays.setTransparency(overlays_ids[1], 150); 149 | 150 | setTimeout(step_2, 11000); 151 | } 152 | 153 | function step_2() { 154 | console.log('--------------- step_2'); 155 | console.log(''); 156 | 157 | console.log('Call get overlays ids'); 158 | var overlays_ids = streamlabs_overlays.getIds(); 159 | console.log(overlays_ids); 160 | 161 | for (let overlayid of overlays_ids) { 162 | console.log('Call set overlay transparency 20/255'); 163 | streamlabs_overlays.setTransparency(overlayid, 180); 164 | } 165 | 166 | win1.setBounds({width: 400, height: 200, x: 100, y: 100 }); 167 | let win1_rect = win1.getBounds(); 168 | streamlabs_overlays.setPosition(overlays_ids[0], win1_rect.x, win1_rect.y, Number(win1_rect.width), Number(win1_rect.height)); 169 | 170 | win2.setBounds({width: 150, height: 400, x: 100, y: 300 }); 171 | let win2_rect = win2.getBounds(); 172 | streamlabs_overlays.setPosition(overlays_ids[1], win2_rect.x, win2_rect.y, Number(win2_rect.width), Number(win2_rect.height)); 173 | 174 | win3.setBounds({width: 200, height: 500, x: 250, y: 300 }); 175 | let win3_rect = win3.getBounds(); 176 | streamlabs_overlays.setPosition(overlays_ids[2], win3_rect.x, win3_rect.y, Number(win3_rect.width), Number(win3_rect.height)); 177 | 178 | setTimeout(step_3, 11000); 179 | } 180 | 181 | function step_3() { 182 | console.log('--------------- step_2'); 183 | console.log(''); 184 | 185 | console.log('Call get overlays ids'); 186 | var overlays_ids = streamlabs_overlays.getIds(); 187 | console.log(overlays_ids); 188 | 189 | for (let overlayid of overlays_ids) { 190 | console.log('Call set overlay transparency 20/255'); 191 | streamlabs_overlays.setTransparency(overlayid, 180); 192 | } 193 | 194 | win1.setBounds({width: 600, height: 300, x: 100, y: 100 }); 195 | let win1_rect = win1.getBounds(); 196 | streamlabs_overlays.setPosition(overlays_ids[0], win1_rect.x, win1_rect.y, Number(win1_rect.width), Number(win1_rect.height)); 197 | 198 | win2.setBounds({width: 300, height: 600, x: 100, y: 400 }); 199 | let win2_rect = win2.getBounds(); 200 | streamlabs_overlays.setPosition(overlays_ids[1], win2_rect.x, win2_rect.y, Number(win2_rect.width), Number(win2_rect.height)); 201 | 202 | win3.setBounds({width: 300, height: 600, x: 400, y: 400 }); 203 | let win3_rect = win3.getBounds(); 204 | streamlabs_overlays.setPosition(overlays_ids[2], win3_rect.x, win3_rect.y, Number(win3_rect.width), Number(win3_rect.height)); 205 | 206 | setTimeout(step_finish, 11000); 207 | } 208 | 209 | function step_finish() { 210 | streamlabs_overlays.stop(); 211 | app.quit(); 212 | } -------------------------------------------------------------------------------- /examples/example_with_visibility.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow } = require("electron") 2 | const electron = require('electron') 3 | const streamlabs_overlays = require('../build/Debug/game_overlay.node') 4 | const fs = require("fs") 5 | 6 | // 7 | // # command to run this test 8 | // yarn run electron examples\example_with_offscreen.js 9 | // 10 | let test_started = false; 11 | let win1; 12 | let overlayid1; 13 | let win2; 14 | let overlayid2; 15 | let hwnd; 16 | 17 | const frame_rate = 2; 18 | const win_width = 400; 19 | const win_height = 400; 20 | 21 | function createWindow() { 22 | test_started = true; 23 | console.log('--------------- step_createWindow'); 24 | console.log(''); 25 | 26 | streamlabs_overlays.start(); 27 | 28 | win1 = new BrowserWindow({ 29 | show: false, 30 | width: win_width, 31 | height: win_height, 32 | frame: false, 33 | webPreferences: { 34 | offscreen: true, 35 | nodeIntegration: false, 36 | }, 37 | }) 38 | 39 | win1.loadURL(`https://codepen.io/jasonleewilson/pen/gPrxwX`); 40 | 41 | win1.setSize( win_width, win_height, false); 42 | hwnd = win1.getNativeWindowHandle(); 43 | console.log(hwnd); 44 | 45 | overlayid1 = streamlabs_overlays.addHWND(hwnd); 46 | let win1_rect = win1.getBounds(); 47 | streamlabs_overlays.setPosition(overlayid1, 100, 200, Number(win1_rect.width), Number(win1_rect.height)); 48 | 49 | win1.webContents.on('paint', (event, dirty, image) => { 50 | streamlabs_overlays.paintOverlay(overlayid1, image.getSize().width, image.getSize().height, image.getBitmap()); 51 | }) 52 | 53 | win1.webContents.setFrameRate(frame_rate); 54 | win1.webContents.invalidate(); 55 | 56 | 57 | win2 = new BrowserWindow({ 58 | show: false, 59 | width: win_width, 60 | height: win_height, 61 | frame: false, 62 | webPreferences: { 63 | offscreen: true, 64 | nodeIntegration: false, 65 | }, 66 | }) 67 | 68 | win2.loadURL(`https://time.is/`); 69 | 70 | win2.setSize( win_width, win_height, false); 71 | hwnd = win2.getNativeWindowHandle(); 72 | console.log(hwnd); 73 | overlayid2 = streamlabs_overlays.addHWND(hwnd); 74 | 75 | let win2_rect = win2.getBounds(); 76 | streamlabs_overlays.setPosition(overlayid2, 200, 100, Number(win2_rect.width), Number(win2_rect.height)); 77 | 78 | win2.webContents.on('paint', (event, dirty, image) => { 79 | streamlabs_overlays.paintOverlay(overlayid2, image.getSize().width, image.getSize().height, image.getBitmap()); 80 | 81 | }) 82 | 83 | win2.webContents.setFrameRate(frame_rate); 84 | win2.webContents.invalidate(); 85 | 86 | streamlabs_overlays.setVisibility(overlayid1, true); 87 | streamlabs_overlays.setVisibility(overlayid2, false); 88 | 89 | streamlabs_overlays.show(); 90 | 91 | win1.on("closed", () => { win1 = null }); 92 | win2.on("closed", () => { win2 = null }); 93 | 94 | setTimeout(step_1, 4000); 95 | } 96 | 97 | app.on("ready", createWindow); 98 | 99 | app.on("window-all-closed", () => { 100 | app.quit(); 101 | }) 102 | 103 | app.on("activate", () => { 104 | if (!test_started) { 105 | createWindow(); 106 | } 107 | }) 108 | 109 | function step_1() { 110 | console.log('--------------- step_1'); 111 | console.log(''); 112 | 113 | streamlabs_overlays.setVisibility(overlayid1, false); 114 | streamlabs_overlays.setVisibility(overlayid2, true); 115 | 116 | setTimeout(step_2, 4000); 117 | } 118 | 119 | function step_2() { 120 | console.log('--------------- step_2'); 121 | console.log(''); 122 | 123 | streamlabs_overlays.hide(); 124 | 125 | setTimeout(step_3, 4000); 126 | } 127 | 128 | function step_3() { 129 | console.log('--------------- step_3'); 130 | console.log(''); 131 | 132 | streamlabs_overlays.show(); 133 | 134 | setTimeout(step_finish, 4000); 135 | } 136 | 137 | function step_finish() { 138 | streamlabs_overlays.stop(); 139 | app.quit(); 140 | } -------------------------------------------------------------------------------- /include/overlay_logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | const std::string getTimeStamp(); 9 | 10 | extern std::ofstream log_output_file; 11 | extern bool log_output_disabled; 12 | 13 | #define log_info if (log_output_disabled || !log_output_file.is_open()) {} else log_output_file << "INF:" << getTimeStamp() << ": " 14 | #define log_debug if (log_output_disabled || !log_output_file.is_open()) {} else log_output_file << "DBG:" << getTimeStamp() << ": " 15 | #define log_error if (log_output_disabled || !log_output_file.is_open()) {} else log_output_file << "ERR:" << getTimeStamp() << ": " 16 | 17 | void logging_start(std::string log_path); 18 | void logging_end(); -------------------------------------------------------------------------------- /include/overlay_paint_frame.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct overlay_frame_js; 4 | 5 | struct overlay_frame 6 | { 7 | 8 | public: 9 | void get_array( void ** array_ref, size_t * array_size); 10 | 11 | overlay_frame(overlay_frame_js * set_data); 12 | virtual ~overlay_frame(); 13 | 14 | protected: 15 | overlay_frame_js * data; 16 | }; 17 | -------------------------------------------------------------------------------- /include/overlay_paint_frame_js.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct overlay_frame_js 6 | { 7 | napi_ref array_cache_ref; 8 | napi_env env_ref; 9 | 10 | overlay_frame_js(napi_env env, napi_value array); 11 | void get_array( void ** array_ref, size_t * array_size); 12 | void clean(); 13 | }; 14 | -------------------------------------------------------------------------------- /include/sl_overlay_api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "stdafx.h" 3 | #include 4 | 5 | struct overlay_frame; 6 | class smg_overlays; 7 | 8 | // char* params like url - functions get ownership of that pointer and clean memory when finish with it 9 | int WINAPI start_overlays_thread(); 10 | int WINAPI stop_overlays_thread(); 11 | std::string get_thread_status_name(); 12 | 13 | int WINAPI show_overlays(); 14 | int WINAPI hide_overlays(); 15 | bool WINAPI is_overlays_hidden(); 16 | 17 | int WINAPI add_overlay_by_hwnd(const void* hwnd_array, size_t array_size); 18 | std::shared_ptr WINAPI get_overlays(); 19 | int WINAPI get_overlays_count(); 20 | int WINAPI remove_overlay(int id); 21 | 22 | int WINAPI set_overlay_position(int id, int x, int y, int width, int height); 23 | int WINAPI paint_overlay_from_buffer(int overlay_id, const void* image_array, size_t array_size, int width, int height); 24 | int WINAPI paint_overlay_cached_buffer(int overlay_id, std::shared_ptr, int width, int height); 25 | int WINAPI set_overlay_transparency(int id, int transparency); 26 | int WINAPI set_overlay_visibility(int id, bool visibility); 27 | int WINAPI set_overlay_autohide(int id, int autohide_timeout, int autohide_transparency); 28 | 29 | int WINAPI set_callback_for_keyboard_input(int (*ptr)(WPARAM, LPARAM)); 30 | int WINAPI set_callback_for_mouse_input(int (*ptr)(WPARAM, LPARAM)); 31 | int WINAPI set_callback_for_switching_input(int (*ptr)()); 32 | 33 | int WINAPI use_callback_for_keyboard_input(WPARAM wParam, LPARAM lParam); 34 | int WINAPI use_callback_for_mouse_input(WPARAM wParam, LPARAM lParam); 35 | int WINAPI use_callback_for_switching_input(); 36 | 37 | int WINAPI switch_overlays_user_input(bool mode_active); -------------------------------------------------------------------------------- /include/sl_overlay_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "overlay_paint_frame.h" 4 | #include "stdafx.h" 5 | 6 | extern wchar_t const g_szWindowClass[]; 7 | 8 | enum class overlay_status : int 9 | { 10 | creating = 1, 11 | source_ready, 12 | working, 13 | destroing 14 | }; 15 | 16 | class overlay_window 17 | { 18 | protected: 19 | RECT rect; 20 | bool manual_position; 21 | std::mutex rect_access; 22 | int overlay_transparency; 23 | bool overlay_visibility; 24 | 25 | bool content_updated; 26 | bool content_set; 27 | std::shared_ptr frame; 28 | std::mutex frame_access; 29 | 30 | int autohide_after; 31 | ULONGLONG last_content_chage_ticks; 32 | bool autohidden; 33 | int autohide_by_transparency; 34 | 35 | overlay_window(); 36 | 37 | public: 38 | RECT get_rect(); 39 | bool set_rect(RECT& new_rect); 40 | bool apply_new_rect(RECT& new_rect); 41 | bool set_new_position(int x, int y); 42 | bool apply_size_from_orig(); 43 | 44 | bool create_window(); 45 | bool ready_to_create_overlay(); 46 | bool set_cached_image(std::shared_ptr save_frame); 47 | virtual bool create_window_content_buffer() = 0; 48 | virtual bool apply_image_from_buffer(const void* image_array, size_t array_size, int width, int height) = 0; 49 | virtual void paint_to_window(HDC window_hdc) = 0; 50 | virtual void create_render_target(ID2D1Factory* m_pDirect2dFactory){}; 51 | bool is_content_updated(); 52 | void set_transparency(int transparency, bool save_as_normal = true); 53 | int get_transparency(); 54 | void set_visibility(bool visibility, bool overlays_shown); 55 | bool is_visible(); 56 | void apply_interactive_mode(bool is_intercepting); 57 | void set_autohide(int timeout, int transparency); 58 | bool reset_autohide(); 59 | virtual void clean_resources(); 60 | 61 | virtual std::string get_status() = 0; 62 | 63 | void check_autohide(); 64 | void reset_autohide_timer(); 65 | 66 | virtual ~overlay_window(); 67 | 68 | overlay_status status; 69 | int id; 70 | HWND orig_handle; 71 | HWND overlay_hwnd; 72 | }; 73 | 74 | class overlay_window_gdi : public overlay_window 75 | { 76 | HBITMAP hbmp; 77 | HDC hdc; 78 | bool g_bDblBuffered = false; 79 | 80 | public: 81 | overlay_window_gdi(); 82 | virtual void clean_resources() override; 83 | 84 | virtual bool apply_image_from_buffer(const void* image_array, size_t array_size, int width, int height) override; 85 | virtual bool create_window_content_buffer() override; 86 | virtual void paint_to_window(HDC window_hdc) override; 87 | void set_dbl_buffering(bool enable); 88 | 89 | virtual std::string get_status(); 90 | }; 91 | 92 | class overlay_window_direct2d : public overlay_window 93 | { 94 | ID2D1HwndRenderTarget* m_pRenderTarget; 95 | ID2D1Bitmap* m_pBitmap; 96 | 97 | public: 98 | overlay_window_direct2d(); 99 | virtual void clean_resources() override; 100 | 101 | virtual bool apply_image_from_buffer(const void* image_array, size_t array_size, int width, int height) override; 102 | virtual bool create_window_content_buffer() override; 103 | virtual void paint_to_window(HDC window_hdc) override; 104 | virtual void create_render_target(ID2D1Factory* m_pDirect2dFactory) override; 105 | 106 | virtual std::string get_status(); 107 | }; -------------------------------------------------------------------------------- /include/sl_overlays.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "stdafx.h" 5 | 6 | DWORD WINAPI overlay_thread_func(void* data); 7 | 8 | class overlay_window; 9 | 10 | class smg_overlays 11 | { 12 | static std::shared_ptr instance; 13 | bool quiting; 14 | 15 | void hide_overlays(); 16 | void showup_overlays(); 17 | void apply_interactive_mode_view(); 18 | 19 | public: 20 | mutable std::shared_mutex overlays_list_access; 21 | bool showing_overlays; 22 | 23 | static std::shared_ptr get_instance(); 24 | 25 | public: 26 | std::list> showing_windows; 27 | 28 | smg_overlays(); 29 | virtual ~smg_overlays(); 30 | void init(); 31 | void deinit(); 32 | 33 | void create_windows_overlays(); 34 | void create_window_for_overlay(std::shared_ptr& overlay); 35 | void create_overlay_window_class(); 36 | 37 | int create_overlay_window_by_hwnd(HWND hwnd); 38 | 39 | size_t get_count(); 40 | std::shared_ptr get_overlay_by_id(int overlay_id); 41 | std::shared_ptr get_overlay_by_window(HWND overlay_window); 42 | std::vector get_ids(); 43 | bool is_inside_overlay(int x , int y); 44 | 45 | bool remove_overlay(std::shared_ptr overlay); 46 | bool on_window_destroy(HWND window); 47 | bool on_overlay_destroy(std::shared_ptr overlay); 48 | 49 | void quit(); 50 | 51 | //redirect user input 52 | bool is_intercepting = false; 53 | void hook_user_input(); 54 | void unhook_user_input(); 55 | 56 | //commands 57 | bool process_commands(MSG& msg); 58 | 59 | //events 60 | void on_update_timer(); 61 | 62 | void draw_overlay(HWND& hWnd); 63 | 64 | BOOL g_bDblBuffered = FALSE; 65 | ID2D1Factory* m_pDirect2dFactory; 66 | bool direct2d_paint = true; 67 | }; -------------------------------------------------------------------------------- /include/sl_overlays_settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class smg_settings 9 | { 10 | public: 11 | const int settings_version; 12 | 13 | int transparency; // o - 255 14 | bool use_color_key; 15 | int redraw_timeout; //ms 16 | 17 | void default_init(); 18 | 19 | smg_settings(); 20 | }; 21 | 22 | extern std::shared_ptr app_settings; 23 | -------------------------------------------------------------------------------- /include/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h : include file for standard system include files, 2 | // or project specific include files that are used frequently, but 3 | // are changed infrequently 4 | // 5 | 6 | #pragma once 7 | 8 | #undef WIN32_LEAN_AND_MEAN 9 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 10 | // Windows Header Files: 11 | #include 12 | 13 | // C RunTime Header Files 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | //#include 20 | #include 21 | //#include 22 | //#include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include // for dbl-buffered painting 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | /* 35 | Concepts: 36 | overlay window - window drawing over all other windows 37 | source window - window from what content will be taken for overlay window 38 | 39 | Threads: 40 | All work with overlays in main thread. Node js api called in its own thread. Some communication between thread made by PostThreadMessages. 41 | Also have mutex to control access to overlays thread data. 42 | one for changes in list of overlays "overlays_list_access" and each overlay object have own mutex for data what can be accessed by other thread. 43 | 44 | Modes: 45 | Node module. - control by node module api. 46 | */ 47 | 48 | enum class sl_overlay_thread_state : int 49 | { 50 | starting = 0x0020, 51 | runing = 0x0040, 52 | stopping = 0x0080, 53 | destoyed = 0x0100 54 | }; 55 | 56 | const int COMMAND_SHOW_OVERLAYS = 1; 57 | const int COMMAND_HIDE_OVERLAYS = 2; 58 | const int COMMAND_QUIT = 4; 59 | const int COMMAND_TAKE_INPUT = 7; 60 | const int COMMAND_RELEASE_INPUT = 8; 61 | 62 | //command for web view thread to close web view 63 | //wParam id 64 | #define WM_SLO_OVERLAY_CLOSE (WM_USER + 33) 65 | 66 | //command for overlay thread to set overlay transparency 67 | //wParam id 68 | //lParam transparency 69 | #define WM_SLO_OVERLAY_TRANSPARENCY (WM_USER + 35) 70 | 71 | //command for overlay thread to set overlay transparency 72 | //wParam id 73 | //lParam transparency 74 | #define WM_SLO_OVERLAY_VISIBILITY (WM_USER + 39) 75 | 76 | //command for web view thread to set overlay position and size 77 | //wParam id 78 | //lParam LPRECT. have to delete it after recive 79 | #define WM_SLO_OVERLAY_POSITION (WM_USER + 36) 80 | 81 | //command for overlay thread to set overlay autohide timeout 82 | //wParam id 83 | //lParam timeout 84 | #define WM_SLO_OVERLAY_SET_AUTOHIDE (WM_USER + 38) 85 | 86 | //signal for overlay thread about source window created and can be used to make overlay for it 87 | #define WM_SLO_SOURCE_CREATED (WM_USER + 40) 88 | 89 | //signal for overlay thread what overlay do not have window anymore and can be deleted 90 | //wParam id 91 | #define WM_SLO_OVERLAY_WINDOW_DESTOYED (WM_USER + 41) 92 | 93 | //signal for overlay thread that it can create window for new overlay 94 | #define WM_SLO_HWND_SOURCE_READY (WM_USER + 43) 95 | 96 | //signal for overlay thread that it can create window for new overlay 97 | #define WM_SLO_OVERLAY_COMMAND (WM_USER + 44) 98 | 99 | 100 | bool set_dpi_awareness(); 101 | 102 | int try_to_get_dpi(HWND window_handle); -------------------------------------------------------------------------------- /include/user_input_callback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "sl_overlay_api.h" 12 | 13 | #include 14 | #include 15 | 16 | struct wm_event_t; 17 | 18 | napi_status napi_create_and_set_named_property(napi_env& env, napi_value& obj, const char* value_name, const int value) noexcept; 19 | 20 | struct callback_method_t 21 | { 22 | napi_ref js_this; 23 | 24 | napi_async_context async_context; 25 | uv_async_t uv_async_this; 26 | napi_env env_this; 27 | 28 | bool initialized; 29 | bool completed; 30 | bool success; 31 | 32 | int result_int; 33 | 34 | int error; 35 | napi_callback set_return; 36 | napi_callback fail; 37 | napi_value result; 38 | 39 | void callback_method_reset() noexcept; 40 | 41 | napi_status set_args_and_call_callback(napi_env env, napi_value callback, napi_value* result); 42 | napi_status callback_method_call_tsf(bool block); 43 | napi_status callback_init(napi_env env, napi_callback_info info, const char* name); 44 | virtual void set_callback() = 0; 45 | int use_callback(WPARAM wParam, LPARAM lParam); 46 | 47 | bool ready; 48 | static bool set_intercept_active(bool) noexcept; 49 | 50 | static bool get_intercept_active() noexcept; 51 | 52 | static void static_async_callback(uv_async_t* handle); 53 | void async_callback(); 54 | 55 | std::mutex send_queue_mutex; 56 | std::queue> to_send; 57 | virtual size_t get_argc_to_cb() = 0; 58 | virtual napi_value* get_argv_to_cb() = 0; 59 | virtual napi_status set_callback_args_values(napi_env env) 60 | { 61 | return napi_ok; 62 | }; 63 | 64 | callback_method_t(); 65 | 66 | virtual ~callback_method_t() ; 67 | callback_method_t(const callback_method_t&) = delete; 68 | callback_method_t& operator=(const callback_method_t&) = delete; 69 | callback_method_t(callback_method_t&&) = delete; 70 | callback_method_t& operator=(callback_method_t&&) = delete; 71 | }; 72 | 73 | struct callback_keyboard_method_t : callback_method_t 74 | { 75 | const static size_t argc_to_cb = 2; 76 | napi_value argv_to_cb[argc_to_cb]; 77 | 78 | size_t get_argc_to_cb() noexcept override 79 | { 80 | return argc_to_cb; 81 | }; 82 | napi_value* get_argv_to_cb() noexcept override 83 | { 84 | return argv_to_cb; 85 | }; 86 | 87 | napi_status set_callback_args_values(napi_env env) override; 88 | void set_callback() override; 89 | }; 90 | 91 | struct callback_mouse_method_t : callback_method_t 92 | { 93 | const static size_t argc_to_cb = 4; 94 | napi_value argv_to_cb[argc_to_cb]; 95 | 96 | size_t get_argc_to_cb() noexcept override 97 | { 98 | return argc_to_cb; 99 | }; 100 | napi_value* get_argv_to_cb() noexcept override 101 | { 102 | return argv_to_cb; 103 | }; 104 | 105 | napi_status set_callback_args_values(napi_env env) override; 106 | void set_callback() override; 107 | }; 108 | 109 | extern callback_keyboard_method_t* user_keyboard_callback_info; 110 | extern callback_mouse_method_t* user_mouse_callback_info; 111 | 112 | int switch_input(); 113 | -------------------------------------------------------------------------------- /index.test.js: -------------------------------------------------------------------------------- 1 | const streamlabsOverlay = require('./index'); 2 | const assert = require('assert'); 3 | 4 | assert(typeof streamlabsOverlay !== 'undefined'); 5 | assert(typeof streamlabsOverlay.start === 'function'); 6 | -------------------------------------------------------------------------------- /npm/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const bindingPath = path.resolve(path.join(__dirname, './game_overlay.node')) 3 | const binding = require(bindingPath); 4 | 5 | module.exports = binding; 6 | -------------------------------------------------------------------------------- /npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@streamlabs/game_overlay", 3 | "private": true, 4 | "description": "Streamlabs Game Overlay module", 5 | "main": "index.js", 6 | "typings": "typings.d.ts", 7 | "author": { 8 | "name": "Streamlabs", 9 | "url": "https://streamlabs.com" 10 | }, 11 | "version": "0.0.1", 12 | "license": "SEE LICENSE IN COPYRIGHT LAW", 13 | "engines": { 14 | "node": ">= 14" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/stream-labs/streamlabs-overlay" 19 | }, 20 | "scripts": { 21 | }, 22 | "dependencies": { 23 | "node-addon-api": "^4.1.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /npm/typings.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /** A unique identifier for an overlay **/ 3 | export type OverlayId = number; 4 | 5 | /** Information about an overlay and its position on the screen */ 6 | export type OverlayInfo = { 7 | /** The internal ID of this overlay */ 8 | id: OverlayId; 9 | /** Width of the window */ 10 | width: number; 11 | /** Height of the window */ 12 | height: number; 13 | /** Horizontal position of the window */ 14 | x: number; 15 | /** Vertical position of the window */ 16 | y: number; 17 | /** Status of overlay, "ok" if everything went fine with that overlay */ 18 | status: String; 19 | }; 20 | 21 | /** Native windows handle (WinAPI), encoded as a Node Buffer **/ 22 | export type HWND = Buffer; 23 | 24 | /** 25 | * Status of the overlay thread 26 | */ 27 | /* 28 | * These aren't typos on our end, backend sends this, I can probably look at it later; still, is C. 29 | * Workaround via enum, do not use these values directly. 30 | */ 31 | export const enum OverlayThreadStatus { 32 | Starting = 'starting', 33 | Running = 'runing', 34 | Stopping = 'stopping', 35 | Destroyed = 'destoyed', 36 | } 37 | 38 | /** 39 | * Start the overlay thread. This is required before any other operations 40 | * can be performed (aside from `getStatus`). 41 | * Can work again only when getStatus returns `destroyed`. 42 | * 43 | * Return: 1 if everything went fine. 44 | */ 45 | export function start(logPath: String): number; 46 | 47 | /** 48 | * Stop the overlay thread. No operations other than `start` and `getStatus` should be performed 49 | * after invoking this. To invoce `start` should wait till thread status become `destroyed` 50 | * 51 | * Return: 1 if everything went fine. 52 | */ 53 | export function stop(): number; 54 | 55 | /** 56 | * Returns the number of overlays currently active 57 | */ 58 | export function getCount(): number; 59 | 60 | /** 61 | * Get IDs of all registered overlays 62 | * 63 | * @returns An array of overlay IDs 64 | */ 65 | export function getIds(): OverlayId[]; 66 | 67 | /** 68 | * Get information about a specific overlay 69 | * 70 | * @param id ID of the overlay to get info for 71 | * @see {OverlayInfo} 72 | */ 73 | export function getInfo(id: OverlayId): OverlayInfo; 74 | 75 | /** Show overlays */ 76 | export function show(): void; 77 | 78 | /** Hide overlays */ 79 | export function hide(): void; 80 | 81 | /** 82 | * Add an overlay to an existing window. 83 | * 84 | * @param hwnd The native Windows handle of the window to be set as an overlay 85 | * @returns ID of the overlay that was created 86 | * @see {HWND} 87 | * @example 88 | * const win = new BrowserWindow({}); 89 | * win.loadURL('https://streamlabs.com'); 90 | * const hwnd = win.getNativeWindowHandle(); 91 | * overlay.addHWND(hwnd); 92 | */ 93 | export function addHWND(hwnd: HWND): OverlayId; 94 | 95 | /** 96 | * Set overlay's position 97 | * 98 | * @param id ID of the overlay to be positioned 99 | * @param x Horizontal position 100 | * @param y Vertical position 101 | * @param width Overlay's width 102 | * @param height Overlay's height 103 | */ 104 | export function setPosition( 105 | id: OverlayId, 106 | x: number, 107 | y: number, 108 | width: number, 109 | height: number, 110 | ): void; 111 | 112 | /** 113 | * Set an overlay's transparency 114 | * 115 | * @param overlayId ID of the overlay to set the transparency for 116 | * @param transparency A positive integer from 0-255 indicating the transparency to set the overlay to, where 255 indicates opaque 117 | */ 118 | export function setTransparency(overlayId: OverlayId, transparency: number): void; 119 | 120 | /** 121 | * Set an overlay's visibility 122 | * 123 | * @param overlayId ID of the overlay to set the visibility for 124 | * @param visibility if false the overlay stay hidden after even after show() 125 | */ 126 | export function setVisibility(overlayId: OverlayId, transparency: boolean): void; 127 | 128 | /** 129 | * Set an overlay's autohide 130 | * 131 | * @param overlayId ID of the overlay to set the transparency for 132 | * @param autohideTimeout A number of seconds. Overlay should be repainted at least once in that amount of seconds to stay visible. 133 | * @param autohideTransparency Transparency that overlay will have while in a hidden state. 134 | */ 135 | export function setAutohide(overlayId: OverlayId, autohideTimeout: number, autohideTransparency: number): void; 136 | 137 | /** 138 | * Send image from electron window to be painted on overlay 139 | * 140 | * @param overlayId ID of the overlay to set 141 | * @param width width of image in buffer 142 | * @param height height of image in buffer 143 | * @param image buffer with native image what electron gives 144 | * @returns a number : 145 | * 1 if it fails 146 | * 0 if overlay expected other image size, it will try to resize to it( should be painted again later) 147 | * 1 for success 148 | * @example 149 | * win.webContents.on('paint', (event, dirty, image) => { 150 | * if ( streamlabs_overlays.paintOverlay(overlayid, image.getSize().width, image.getSize().height, image.getBitmap()) == 0 ) 151 | * { 152 | * win.webContents.invalidate(); 153 | * } 154 | * }) 155 | */ 156 | export function paintOverlay(overlayId: OverlayId, width: number, height: number, image: Buffer): number; 157 | 158 | /** 159 | * Remove an overlay 160 | * 161 | * @param id ID of the overlay to be removed 162 | */ 163 | export function remove(id: OverlayId): void; 164 | 165 | /** 166 | * Get the status of the overlay thread 167 | * 168 | * @see {OverlayThreadStatus} 169 | */ 170 | export function getStatus(): OverlayThreadStatus; 171 | 172 | /** 173 | * Set callback for mouse events 174 | * setMouseCallback( (eventType, x, y, modifier) => { 175 | * 176 | */ 177 | export function setMouseCallback(callback: Function): void; 178 | 179 | /** 180 | * Set callback for keyboard events 181 | * setKeyboardCallback( (eventType, keyCode) => { 182 | * 183 | */ 184 | export function setKeyboardCallback(callback: Function): void; 185 | 186 | /** 187 | * Switch on/off interactive mode for overlays 188 | * In this mode overlay module intercept user keyboard and mouse events and use callbacks to send them to frontend 189 | * 190 | */ 191 | export function switchInteractiveMode( active: Boolean ): void; 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@streamlabs/game_overlay", 3 | "private": true, 4 | "description": "Streamlabs Game Overlay module", 5 | "gypfile": true, 6 | "main": "npm/index.js", 7 | "typings": "npm/typings.d.ts", 8 | "author": { 9 | "name": "Streamlabs", 10 | "url": "https://streamlabs.com" 11 | }, 12 | "version": "0.0.40", 13 | "license": "SEE LICENSE IN COPYRIGHT LAW", 14 | "engines": { 15 | "node": ">= 14" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/stream-labs/streamlabs-overlay" 20 | }, 21 | "scripts": { 22 | "pack": "node scripts/pack.js", 23 | "test": "", 24 | "get-version": "echo %npm_package_version%" 25 | }, 26 | "prettier": { 27 | "singleQuote": true, 28 | "printWidth": 100, 29 | "endOfLine": "lf", 30 | "trailingComma": "all" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "12.7.2", 34 | "electron": "29.4.3", 35 | "fs-extra": "9.0.0", 36 | "prettier": "1.16.4", 37 | "minimist":"1.2.6", 38 | "colors": "^1.4.0", 39 | "fs": "^0.0.1-security", 40 | "node-addon-api": "^7.1.1", 41 | "path": "^0.12.7", 42 | "shelljs": "^0.8.5", 43 | "tar": "6.1.11" 44 | }, 45 | "dependencies": { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Overlay 2 | `overlay` - it is a window what try to stay over any other windows( even fullscreen games ) and show content of some `source`. 3 | 4 | `source` - can be other window like chat or cpu monitor. 5 | 6 | It should be build as nodejs module. 7 | 8 | ## NodeJS Module 9 | ### Build 10 | There is cmake project file in repository what can be used to make node module. 11 | 12 | To setup env 13 | ``` 14 | yarn install 15 | ``` 16 | 17 | To configure a build 18 | ``` 19 | mkdir build 20 | cd build 21 | cmake -G "Visual Studio 16 2019" -A x64 ../ 22 | ``` 23 | 24 | And to make a build 25 | ``` 26 | cmake --build . --config Release 27 | ``` 28 | 29 | #### Requirements 30 | - node 31 | - yarn 32 | - msbuild (vs studio make tools ) 33 | 34 | ### Module use examples 35 | Examples to show api usage for simple usecases. 36 | ``` 37 | yarn electron examples\example_with_offscreen.js 38 | yarn electron examples\example_interactivity_console.js 39 | yarn electron examples\example_interactivity_window.js 40 | ``` 41 | 42 | 43 | ### Module API 44 | Each overlay has ID by which it can be adressed in api. 45 | 46 | Thread what control overlays have to be started and stoped explicitly by module user 47 | - `start()` 48 | - `stop()` 49 | 50 | For now overlays can be shown and hidden all together 51 | - `show()` 52 | - `hide()` 53 | 54 | To get basic info about overlays 55 | - `getCount()` 56 | - `getIds()` it return list of overlay ids. 57 | - `getInfo(overlay_id)` 58 | 59 | To create, setup and remove overlay 60 | - `addHWND(hwnd)` return overlay id 61 | - `setPosition(overlay_id, x, y, width, height)` 62 | - `setTransparency(overlay_id, transparency)` from 0 to 255 like in SetLayeredWindowAttributes 63 | - `reload(overlay_id)` send web view a command to reload current page 64 | - `remove(overlay_id)` 65 | - `paintOverlay(overlay_id, width, height, bitmap)` 66 | 67 | For interective mode set callbacks and switch on/off. See examples\example_with_hwnd_node.js. 68 | - `setMouseCallback(callback)` 69 | - `setKeyabordCallback(callback)` 70 | - `switchInteractiveMode()` 71 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "sl_overlays.h" 16 | #include "stdafx.h" 17 | 18 | #include 19 | #include 20 | 21 | #include "overlay_logging.h" 22 | #include "sl_overlay_window.h" 23 | #include "sl_overlays_settings.h" 24 | 25 | std::shared_ptr app_settings; 26 | 27 | HANDLE overlays_thread = nullptr; 28 | DWORD overlays_thread_id = 0; 29 | sl_overlay_thread_state thread_state = sl_overlay_thread_state::destoyed; 30 | std::mutex thread_state_mutex; 31 | 32 | UINT_PTR OVERLAY_UPDATE_TIMER = 0; 33 | 34 | 35 | DWORD WINAPI overlay_thread_func(void* data) 36 | { 37 | app_settings = std::make_shared(); 38 | 39 | std::shared_ptr app = smg_overlays::get_instance(); 40 | 41 | set_dpi_awareness(); 42 | 43 | // Init COM and double-buffered painting 44 | HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE | COINIT_SPEED_OVER_MEMORY); 45 | 46 | if (SUCCEEDED(hr)) 47 | { 48 | app->init(); 49 | 50 | OVERLAY_UPDATE_TIMER = SetTimer(0, 0, app_settings->redraw_timeout, (TIMERPROC) nullptr); 51 | 52 | thread_state_mutex.lock(); 53 | thread_state = sl_overlay_thread_state::runing; 54 | thread_state_mutex.unlock(); 55 | 56 | // Main message loop 57 | MSG msg; 58 | while (GetMessage(&msg, nullptr, 0, 0)) 59 | { 60 | //log_debug << "APP: wnd proc msg id " << msg.message << " for hwnd " << msg.hwnd << std::endl; 61 | bool catched = false; 62 | 63 | switch (msg.message) 64 | { 65 | case WM_SLO_OVERLAY_CLOSE: 66 | { 67 | log_info << "APP: WM_SLO_OVERLAY_CLOSE " << (int)msg.wParam << std::endl; 68 | auto closed = app->get_overlay_by_id((int)msg.wParam); 69 | app->remove_overlay(closed); 70 | catched = true; 71 | } 72 | break; 73 | case WM_SLO_OVERLAY_POSITION: 74 | { 75 | log_info << "APP: WM_SLO_OVERLAY_POSITION " << (int)msg.wParam << std::endl; 76 | std::shared_ptr overlay = app->get_overlay_by_id((int)msg.wParam); 77 | RECT* new_rect = reinterpret_cast(msg.lParam); 78 | if (new_rect != nullptr) 79 | { 80 | if (overlay != nullptr) 81 | { 82 | log_debug << "APP: WM_SLO_OVERLAY_POSITION " << new_rect->left << " " << new_rect->top << std::endl; 83 | overlay->apply_new_rect(*new_rect); 84 | } 85 | delete new_rect; 86 | } 87 | catched = true; 88 | } 89 | break; 90 | 91 | case WM_SLO_OVERLAY_TRANSPARENCY: 92 | { 93 | log_info << "APP: WM_SLO_OVERLAY_TRANSPARENCY " << (int)msg.wParam << ", " << (int)msg.lParam << std::endl; 94 | std::shared_ptr overlay = app->get_overlay_by_id((int)msg.wParam); 95 | 96 | if (overlay != nullptr) 97 | { 98 | overlay->set_transparency((int)msg.lParam); 99 | } 100 | catched = true; 101 | } 102 | break; 103 | case WM_SLO_OVERLAY_VISIBILITY: 104 | { 105 | log_info << "APP: WM_SLO_OVERLAY_VISIBILITY " << (int)msg.wParam << ", " << (int)msg.lParam << std::endl; 106 | std::shared_ptr overlay = app->get_overlay_by_id((int)msg.wParam); 107 | 108 | if (overlay != nullptr) 109 | { 110 | 111 | overlay->set_visibility((bool)msg.lParam, app->showing_overlays); 112 | } 113 | catched = true; 114 | } 115 | break; 116 | case WM_SLO_OVERLAY_SET_AUTOHIDE: 117 | { 118 | log_info << "APP: WM_SLO_OVERLAY_SET_AUTOHIDE " << (int)msg.wParam << ", " << (int)msg.lParam << std::endl; 119 | std::shared_ptr overlay = app->get_overlay_by_id((int)msg.wParam); 120 | 121 | if (overlay != nullptr) 122 | { 123 | const int autohide_timeout = (int)msg.lParam >> 10; 124 | const int autohide_transparency = (int)msg.lParam % 512; 125 | overlay->set_autohide(autohide_timeout, autohide_transparency); 126 | } 127 | catched = true; 128 | } 129 | break; 130 | case WM_SLO_OVERLAY_WINDOW_DESTOYED: 131 | { 132 | log_info << "APP: WM_OVERLAY_WINDOW_DESTOYED " << (int)msg.wParam << std::endl; 133 | std::shared_ptr overlay = app->get_overlay_by_id((int)msg.wParam); 134 | app->on_overlay_destroy(overlay); 135 | catched = true; 136 | } 137 | break; 138 | case WM_SLO_OVERLAY_COMMAND: 139 | { 140 | catched = app->process_commands(msg); 141 | } 142 | break; 143 | case WM_SLO_HWND_SOURCE_READY: 144 | { 145 | log_info << "APP: WM_SLO_HWND_SOURCE_READY " << (int)msg.wParam << std::endl; 146 | std::shared_ptr overlay = app->get_overlay_by_id((int)msg.wParam); 147 | app->create_window_for_overlay(overlay); 148 | catched = true; 149 | } 150 | break; 151 | case WM_TIMER: 152 | if (static_cast(msg.wParam) == OVERLAY_UPDATE_TIMER) 153 | { 154 | app->on_update_timer(); 155 | catched = true; 156 | } 157 | break; 158 | default: 159 | break; 160 | }; 161 | 162 | if (!catched) 163 | { 164 | TranslateMessage(&msg); 165 | DispatchMessage(&msg); 166 | } 167 | } 168 | 169 | KillTimer(0, OVERLAY_UPDATE_TIMER); 170 | OVERLAY_UPDATE_TIMER = 0; 171 | 172 | CoUninitialize(); 173 | } 174 | 175 | app->deinit(); //todo clean singleton in case some one start thread another time after stop 176 | 177 | log_info << "APP: exit from thread " << std::endl; 178 | 179 | thread_state_mutex.lock(); 180 | 181 | CloseHandle(overlays_thread); 182 | 183 | overlays_thread = nullptr; 184 | overlays_thread_id = 0; 185 | thread_state = sl_overlay_thread_state::destoyed; 186 | 187 | thread_state_mutex.unlock(); 188 | 189 | return 0; 190 | } 191 | 192 | LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 193 | { 194 | switch (message) 195 | { 196 | case WM_CREATE: 197 | {} 198 | break; 199 | case WM_SIZE: 200 | { 201 | auto overlay = smg_overlays::get_instance()->get_overlay_by_window(hWnd); 202 | if (overlay) 203 | { 204 | overlay->create_render_target(smg_overlays::get_instance()->m_pDirect2dFactory); 205 | overlay->create_window_content_buffer(); 206 | } 207 | } 208 | break; 209 | case WM_CLOSE: 210 | { 211 | //todo some how window wants to be closed so need to remove its overlay object 212 | log_info << "APP: WndProc WM_CLOSE for " << hWnd << std::endl; 213 | } 214 | break; 215 | case WM_DESTROY: 216 | { 217 | log_info << "APP: WndProc WM_DESTROY for " << hWnd << std::endl; 218 | smg_overlays::get_instance()->on_window_destroy(hWnd); 219 | 220 | return 0; 221 | } 222 | break; 223 | case WM_ERASEBKGND: 224 | { 225 | // Don't do any erasing here. It's done in WM_PAINT to avoid flicker. 226 | return 1; 227 | } 228 | break; 229 | 230 | case WM_QUIT: 231 | { 232 | log_info << "APP: WndProc WM_QUIT for " << hWnd << std::endl; 233 | } 234 | break; 235 | case WM_PAINT: 236 | { 237 | smg_overlays::get_instance()->draw_overlay(hWnd); 238 | return 0; 239 | } 240 | break; 241 | 242 | default: 243 | break; 244 | } 245 | 246 | return DefWindowProc(hWnd, message, wParam, lParam); 247 | } 248 | -------------------------------------------------------------------------------- /src/module.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "user_input_callback.h" 16 | 17 | #include "sl_overlay_api.h" // NOLINT(build/include) 18 | #include "sl_overlay_window.h" 19 | #include "sl_overlays.h" 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include "overlay_logging.h" 26 | 27 | #include "overlay_paint_frame.h" 28 | #include "overlay_paint_frame_js.h" 29 | 30 | const napi_value failed_ret = nullptr; 31 | napi_value Start(napi_env env, napi_callback_info args) 32 | { 33 | int thread_start_status = 0; 34 | napi_value ret = nullptr; 35 | 36 | size_t argc = 1; 37 | napi_value argv[1]; 38 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 39 | return failed_ret; 40 | 41 | if (argc == 1) 42 | { 43 | napi_status status; 44 | size_t result; 45 | char log_path_name[256]; 46 | status = napi_get_value_string_utf8(env, argv[0], log_path_name, sizeof(log_path_name), &result); 47 | if(status == napi_ok) 48 | { 49 | static std::string log_path = ""; 50 | log_path = std::string( log_path_name ); 51 | logging_start(log_path); 52 | } 53 | log_info << "Start game overlay thread command just called "<< std::endl; 54 | } 55 | 56 | thread_start_status = start_overlays_thread(); 57 | if (thread_start_status != 0) 58 | { 59 | if (user_keyboard_callback_info == nullptr) 60 | { 61 | user_keyboard_callback_info = new callback_keyboard_method_t(); 62 | } 63 | 64 | if (user_mouse_callback_info == nullptr) 65 | { 66 | user_mouse_callback_info = new callback_mouse_method_t(); 67 | } 68 | } 69 | 70 | if (napi_create_int32(env, thread_start_status, &ret) != napi_ok) 71 | return failed_ret; 72 | 73 | return ret; 74 | } 75 | 76 | napi_value Stop(napi_env env, napi_callback_info args) 77 | { 78 | if (user_keyboard_callback_info != nullptr) 79 | { 80 | delete user_keyboard_callback_info; 81 | user_keyboard_callback_info = nullptr; 82 | } 83 | if (user_mouse_callback_info != nullptr) 84 | { 85 | delete user_mouse_callback_info; 86 | user_mouse_callback_info = nullptr; 87 | } 88 | 89 | int thread_stop_status = 0; 90 | napi_value ret = nullptr; 91 | 92 | thread_stop_status = stop_overlays_thread(); 93 | 94 | log_info << "Stop game overlay thread command completed " << std::endl; 95 | logging_end(); 96 | 97 | if (napi_create_int32(env, thread_stop_status, &ret) != napi_ok) 98 | return failed_ret; 99 | 100 | return ret; 101 | } 102 | 103 | napi_value GetStatus(napi_env env, napi_callback_info args) 104 | { 105 | std::string thread_status = get_thread_status_name(); 106 | 107 | napi_value ret = nullptr; 108 | if (napi_create_string_utf8(env, thread_status.c_str(), thread_status.size(), &ret) != napi_ok) 109 | return failed_ret; 110 | 111 | return ret; 112 | } 113 | 114 | napi_value ShowOverlays(napi_env env, napi_callback_info args) 115 | { 116 | show_overlays(); 117 | return failed_ret; 118 | } 119 | 120 | napi_value HideOverlays(napi_env env, napi_callback_info args) 121 | { 122 | hide_overlays(); 123 | return failed_ret; 124 | } 125 | 126 | napi_value GetOverlaysCount(napi_env env, napi_callback_info args) 127 | { 128 | int count = get_overlays_count(); 129 | napi_value ret = nullptr; 130 | 131 | if (napi_create_int32(env, count, &ret) != napi_ok) 132 | return failed_ret; 133 | 134 | return ret; 135 | } 136 | 137 | napi_value AddOverlayHWND(napi_env env, napi_callback_info args) 138 | { 139 | napi_value ret = nullptr; 140 | 141 | size_t argc = 1; 142 | napi_value argv[1]; 143 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 144 | return failed_ret; 145 | 146 | int crated_overlay_id = -1; 147 | if (argc == 1) 148 | { 149 | void* incoming_array = nullptr; 150 | size_t array_lenght = 0; 151 | if (napi_get_buffer_info(env, argv[0], &incoming_array, &array_lenght) != napi_ok) 152 | return failed_ret; 153 | 154 | if (incoming_array != nullptr) 155 | { 156 | log_info << "APP: AddOverlayHWND " << argc << std::endl; 157 | 158 | crated_overlay_id = add_overlay_by_hwnd(incoming_array, array_lenght); 159 | incoming_array = nullptr; 160 | } else 161 | { 162 | log_error << "APP: AddOverlayHWND failed to get hwnd" << argc << std::endl; 163 | } 164 | } 165 | 166 | if (napi_create_int32(env, crated_overlay_id, &ret) != napi_ok) 167 | return failed_ret; 168 | 169 | return ret; 170 | } 171 | 172 | napi_value RemoveOverlay(napi_env env, napi_callback_info args) 173 | { 174 | size_t argc = 1; 175 | napi_value argv[1]; 176 | int32_t overlay_id; 177 | napi_value ret = nullptr; 178 | 179 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 180 | return failed_ret; 181 | 182 | if (napi_get_value_int32(env, argv[0], &overlay_id) != napi_ok) 183 | return failed_ret; 184 | 185 | log_info << "APP: RemoveOverlay " << overlay_id << std::endl; 186 | remove_overlay(overlay_id); 187 | 188 | return ret; 189 | } 190 | 191 | napi_value SwitchToInteractive(napi_env env, napi_callback_info args) 192 | { 193 | if (!user_keyboard_callback_info->ready || !user_mouse_callback_info->ready) 194 | { 195 | log_info << "APP: SwitchToInteractive rejected as callbacks not set" << std::endl; 196 | return failed_ret; 197 | } 198 | 199 | napi_value ret = nullptr; 200 | size_t argc = 1; 201 | napi_value argv[1]; 202 | bool switch_to; 203 | int switched = -1; 204 | 205 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 206 | return failed_ret; 207 | 208 | if (napi_get_value_bool(env, argv[0], &switch_to) != napi_ok) 209 | return failed_ret; 210 | 211 | bool check_visibility_for_switch = (!is_overlays_hidden()) && switch_to || (!switch_to); 212 | 213 | if (callback_method_t::get_intercept_active() != switch_to && check_visibility_for_switch) 214 | { 215 | set_callback_for_switching_input(&switch_input); // so module can switch itself off by some command 216 | 217 | switch_input(); 218 | 219 | log_info << "APP: SwitchToInteractive " << callback_method_t::get_intercept_active() << std::endl; 220 | 221 | switched = 1; 222 | } 223 | 224 | if (napi_create_int32(env, switched, &ret) != napi_ok) 225 | return failed_ret; 226 | 227 | return ret; 228 | } 229 | 230 | napi_value SetKeyboardCallback(napi_env env, napi_callback_info args) 231 | { 232 | log_info << "APP: SetKeyboardCallback " << std::endl; 233 | if (user_keyboard_callback_info->ready) 234 | { 235 | user_keyboard_callback_info->ready = false; 236 | napi_delete_reference(env, user_keyboard_callback_info->js_this); 237 | } 238 | 239 | size_t argc = 1; 240 | napi_value argv[1]; 241 | napi_value js_this; 242 | napi_value js_callback; 243 | napi_valuetype is_function = napi_undefined; 244 | 245 | if (napi_get_cb_info(env, args, &argc, argv, &js_this, 0) != napi_ok) 246 | return failed_ret; 247 | 248 | //check if js side of callback is valid 249 | if (napi_get_prototype(env, argv[0], &js_callback) != napi_ok) 250 | return failed_ret; 251 | 252 | if (napi_typeof(env, js_callback, &is_function) != napi_ok) 253 | return failed_ret; 254 | 255 | if (is_function == napi_function) 256 | { 257 | //save reference and go to creating threadsafe function 258 | if (napi_create_reference(env, argv[0], 1, &user_keyboard_callback_info->js_this) != napi_ok) 259 | return failed_ret; 260 | 261 | user_keyboard_callback_info->callback_init(env, args, "func_keyboard"); 262 | } 263 | 264 | return nullptr; 265 | } 266 | 267 | napi_value SetMouseCallback(napi_env env, napi_callback_info args) 268 | { 269 | log_info << "APP: SetMouseCallback " << std::endl; 270 | if (user_mouse_callback_info->ready) 271 | { 272 | user_mouse_callback_info->ready = false; 273 | napi_delete_reference(env, user_mouse_callback_info->js_this); 274 | } 275 | 276 | size_t argc = 1; 277 | napi_value argv[1]; 278 | napi_value js_this; 279 | napi_value js_callback; 280 | napi_valuetype is_function = napi_undefined; 281 | 282 | if (napi_get_cb_info(env, args, &argc, argv, &js_this, 0) != napi_ok) 283 | return failed_ret; 284 | 285 | //check if js side of callback is valid 286 | if (napi_get_prototype(env, argv[0], &js_callback) != napi_ok) 287 | return failed_ret; 288 | 289 | if (napi_typeof(env, js_callback, &is_function) != napi_ok) 290 | return failed_ret; 291 | 292 | if (is_function == napi_function) 293 | { 294 | //save reference and go to creating threadsafe function 295 | if (napi_create_reference(env, argv[0], 1, &user_mouse_callback_info->js_this) != napi_ok) 296 | return failed_ret; 297 | 298 | user_mouse_callback_info->callback_init(env, args, "func_mouse"); 299 | } 300 | 301 | return nullptr; 302 | } 303 | 304 | napi_value GetOverlayInfo(napi_env env, napi_callback_info args) 305 | { 306 | size_t argc = 1; 307 | napi_value argv[1]; 308 | int32_t overlay_id; 309 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 310 | return failed_ret; 311 | 312 | if (napi_get_value_int32(env, argv[0], &overlay_id) != napi_ok) 313 | return failed_ret; 314 | 315 | log_info << "APP: GetOverlayInfo look for " << overlay_id << std::endl; 316 | std::shared_ptr requested_overlay = get_overlays()->get_overlay_by_id(overlay_id); 317 | 318 | if (requested_overlay) 319 | { 320 | napi_value ret; 321 | if (napi_create_object(env, &ret) != napi_ok) 322 | return failed_ret; 323 | 324 | RECT overlay_rect = requested_overlay->get_rect(); 325 | 326 | if (napi_create_and_set_named_property(env, ret, "id", requested_overlay->id) != napi_ok) 327 | return failed_ret; 328 | 329 | if (napi_create_and_set_named_property(env, ret, "width", overlay_rect.right - overlay_rect.left) != napi_ok) 330 | return failed_ret; 331 | 332 | if (napi_create_and_set_named_property(env, ret, "height", overlay_rect.bottom - overlay_rect.top) != napi_ok) 333 | return failed_ret; 334 | 335 | if (napi_create_and_set_named_property(env, ret, "x", overlay_rect.left) != napi_ok) 336 | return failed_ret; 337 | 338 | if (napi_create_and_set_named_property(env, ret, "y", overlay_rect.top) != napi_ok) 339 | return failed_ret; 340 | 341 | std::string overlay_status = requested_overlay->get_status(); 342 | napi_value overlay_status_value; 343 | if (napi_create_string_utf8(env, overlay_status.c_str(), overlay_status.size(), &overlay_status_value) == napi_ok) 344 | if (napi_set_named_property(env, ret, "status", overlay_status_value) != napi_ok) 345 | return failed_ret; 346 | 347 | return ret; 348 | } 349 | 350 | return failed_ret; 351 | } 352 | 353 | napi_value GetOverlaysIDs(napi_env env, napi_callback_info args) 354 | { 355 | std::vector ids = get_overlays()->get_ids(); 356 | napi_value ret = nullptr; 357 | 358 | if (napi_create_array(env, &ret) != napi_ok) 359 | return failed_ret; 360 | 361 | for (int i = 0; i < ids.size(); i++) 362 | { 363 | napi_value id; 364 | if (napi_create_int32(env, ids[i], &id) != napi_ok) 365 | return failed_ret; 366 | 367 | if (napi_set_element(env, ret, i, id) != napi_ok) 368 | return failed_ret; 369 | } 370 | 371 | return ret; 372 | } 373 | 374 | napi_value SetOverlayPosition(napi_env env, napi_callback_info args) 375 | { 376 | napi_value ret = nullptr; 377 | 378 | size_t argc = 5; 379 | napi_value argv[5]; 380 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 381 | return failed_ret; 382 | 383 | int position_set_result = -1; 384 | if (argc == 5) 385 | { 386 | int id, x, y, width, height; 387 | 388 | if (napi_get_value_int32(env, argv[0], &id) != napi_ok) 389 | return failed_ret; 390 | 391 | if (napi_get_value_int32(env, argv[1], &x) != napi_ok) 392 | return failed_ret; 393 | 394 | if (napi_get_value_int32(env, argv[2], &y) != napi_ok) 395 | return failed_ret; 396 | 397 | if (napi_get_value_int32(env, argv[3], &width) != napi_ok) 398 | return failed_ret; 399 | 400 | if (napi_get_value_int32(env, argv[4], &height) != napi_ok) 401 | return failed_ret; 402 | 403 | log_info << "APP: SetOverlayPosition " << id << ", size " << width << "x" << height << " at [" << x << ", " << y << "] " << std::endl; 404 | 405 | position_set_result = set_overlay_position(id, x, y, width, height); 406 | } 407 | 408 | if (napi_create_int32(env, position_set_result, &ret) != napi_ok) 409 | return failed_ret; 410 | 411 | return ret; 412 | } 413 | 414 | napi_value PaintOverlay(napi_env env, napi_callback_info args) 415 | { 416 | napi_value ret = nullptr; 417 | 418 | size_t argc = 4; 419 | napi_value argv[4]; 420 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 421 | return failed_ret; 422 | 423 | int painted = -1; 424 | if (argc == 4) 425 | { 426 | int overlay_id = -1; 427 | int width = 0; 428 | int height = 0; 429 | 430 | if (napi_get_value_int32(env, argv[0], &overlay_id) != napi_ok) 431 | return failed_ret; 432 | if (napi_get_value_int32(env, argv[1], &width) != napi_ok) 433 | return failed_ret; 434 | if (napi_get_value_int32(env, argv[2], &height) != napi_ok) 435 | return failed_ret; 436 | 437 | 438 | overlay_frame_js * for_caching_js = new overlay_frame_js(env, argv[3]); 439 | std::shared_ptr for_caching = std::make_shared(for_caching_js); 440 | 441 | painted = paint_overlay_cached_buffer(overlay_id, for_caching, width, height); 442 | } 443 | 444 | if (napi_create_int32(env, painted, &ret) != napi_ok) 445 | return failed_ret; 446 | 447 | return ret; 448 | } 449 | 450 | napi_value SetOverlayTransparency(napi_env env, napi_callback_info args) 451 | { 452 | napi_value ret = nullptr; 453 | 454 | size_t argc = 2; 455 | napi_value argv[2]; 456 | 457 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 458 | return failed_ret; 459 | 460 | int set_transparency_result = -1; 461 | if (argc == 2) 462 | { 463 | int overlay_id = -1; 464 | int overlay_transparency; 465 | 466 | if (napi_get_value_int32(env, argv[0], &overlay_id) != napi_ok) 467 | return failed_ret; 468 | 469 | if (napi_get_value_int32(env, argv[1], &overlay_transparency) != napi_ok) 470 | return failed_ret; 471 | 472 | log_info << "APP: SetOverlayTransparency " << overlay_transparency << std::endl; 473 | set_transparency_result = set_overlay_transparency(overlay_id, overlay_transparency); 474 | } 475 | 476 | if (napi_create_int32(env, set_transparency_result, &ret) != napi_ok) 477 | return failed_ret; 478 | 479 | return ret; 480 | } 481 | 482 | napi_value SetOverlayVisibility(napi_env env, napi_callback_info args) 483 | { 484 | napi_value ret = nullptr; 485 | 486 | size_t argc = 2; 487 | napi_value argv[2]; 488 | 489 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 490 | return failed_ret; 491 | 492 | int set_overlay_visibility_result = -1; 493 | if (argc == 2) 494 | { 495 | int overlay_id = -1; 496 | bool overlay_visibility; 497 | 498 | if (napi_get_value_int32(env, argv[0], &overlay_id) != napi_ok) 499 | return failed_ret; 500 | 501 | if (napi_get_value_bool(env, argv[1], &overlay_visibility) != napi_ok) 502 | return failed_ret; 503 | 504 | log_info << "APP: SetOverlayVisibility " << overlay_visibility << std::endl; 505 | set_overlay_visibility_result = set_overlay_visibility(overlay_id, overlay_visibility); 506 | } 507 | 508 | if (napi_create_int32(env, set_overlay_visibility_result, &ret) != napi_ok) 509 | return failed_ret; 510 | 511 | return ret; 512 | } 513 | 514 | napi_value SetOverlayAutohide(napi_env env, napi_callback_info args) 515 | { 516 | napi_value ret = nullptr; 517 | 518 | size_t argc = 3; 519 | napi_value argv[3]; 520 | 521 | if (napi_get_cb_info(env, args, &argc, argv, NULL, NULL) != napi_ok) 522 | return failed_ret; 523 | 524 | int set_autohide_result = -1; 525 | if (argc == 2 || argc == 3) 526 | { 527 | int overlay_id = -1; 528 | int autohide_timeout = 0; 529 | int autohide_transparency = 0; 530 | 531 | if (napi_get_value_int32(env, argv[0], &overlay_id) != napi_ok) 532 | return failed_ret; 533 | 534 | if (napi_get_value_int32(env, argv[1], &autohide_timeout) != napi_ok) 535 | return failed_ret; 536 | 537 | if( argc == 3 ) 538 | { 539 | if (napi_get_value_int32(env, argv[2], &autohide_transparency) != napi_ok) 540 | return failed_ret; 541 | } 542 | 543 | log_info << "APP: SetOverlayAutohide " << autohide_timeout << ", " << autohide_transparency << std::endl; 544 | set_autohide_result = set_overlay_autohide(overlay_id, autohide_timeout, autohide_transparency); 545 | } 546 | 547 | if (napi_create_int32(env, set_autohide_result, &ret) != napi_ok) 548 | return failed_ret; 549 | 550 | return ret; 551 | } 552 | 553 | napi_value init(napi_env env, napi_value exports) 554 | { 555 | napi_value fn; 556 | 557 | if (napi_create_function(env, nullptr, 0, Start, nullptr, &fn) != napi_ok) 558 | return failed_ret; 559 | if (napi_set_named_property(env, exports, "start", fn) != napi_ok) 560 | return failed_ret; 561 | 562 | if (napi_create_function(env, nullptr, 0, Stop, nullptr, &fn) != napi_ok) 563 | return failed_ret; 564 | if (napi_set_named_property(env, exports, "stop", fn) != napi_ok) 565 | return failed_ret; 566 | 567 | if (napi_create_function(env, nullptr, 0, GetStatus, nullptr, &fn) != napi_ok) 568 | return failed_ret; 569 | if (napi_set_named_property(env, exports, "getStatus", fn) != napi_ok) 570 | return failed_ret; 571 | 572 | if (napi_create_function(env, nullptr, 0, GetOverlaysCount, nullptr, &fn) != napi_ok) 573 | return failed_ret; 574 | if (napi_set_named_property(env, exports, "getCount", fn) != napi_ok) 575 | return failed_ret; 576 | 577 | if (napi_create_function(env, nullptr, 0, GetOverlaysIDs, nullptr, &fn) != napi_ok) 578 | return failed_ret; 579 | if (napi_set_named_property(env, exports, "getIds", fn) != napi_ok) 580 | return failed_ret; 581 | 582 | if (napi_create_function(env, nullptr, 0, GetOverlayInfo, nullptr, &fn) != napi_ok) 583 | return failed_ret; 584 | if (napi_set_named_property(env, exports, "getInfo", fn) != napi_ok) 585 | return failed_ret; 586 | 587 | if (napi_create_function(env, nullptr, 0, ShowOverlays, nullptr, &fn) != napi_ok) 588 | return failed_ret; 589 | if (napi_set_named_property(env, exports, "show", fn) != napi_ok) 590 | return failed_ret; 591 | 592 | if (napi_create_function(env, nullptr, 0, HideOverlays, nullptr, &fn) != napi_ok) 593 | return failed_ret; 594 | if (napi_set_named_property(env, exports, "hide", fn) != napi_ok) 595 | return failed_ret; 596 | 597 | if (napi_create_function(env, nullptr, 0, AddOverlayHWND, nullptr, &fn) != napi_ok) 598 | return failed_ret; 599 | if (napi_set_named_property(env, exports, "addHWND", fn) != napi_ok) 600 | return failed_ret; 601 | 602 | if (napi_create_function(env, nullptr, 0, SetOverlayPosition, nullptr, &fn) != napi_ok) 603 | return failed_ret; 604 | if (napi_set_named_property(env, exports, "setPosition", fn) != napi_ok) 605 | return failed_ret; 606 | 607 | if (napi_create_function(env, nullptr, 0, PaintOverlay, nullptr, &fn) != napi_ok) 608 | return failed_ret; 609 | if (napi_set_named_property(env, exports, "paintOverlay", fn) != napi_ok) 610 | return failed_ret; 611 | 612 | if (napi_create_function(env, nullptr, 0, SetOverlayTransparency, nullptr, &fn) != napi_ok) 613 | return failed_ret; 614 | if (napi_set_named_property(env, exports, "setTransparency", fn) != napi_ok) 615 | return failed_ret; 616 | 617 | if (napi_create_function(env, nullptr, 0, SetOverlayVisibility, nullptr, &fn) != napi_ok) 618 | return failed_ret; 619 | if (napi_set_named_property(env, exports, "setVisibility", fn) != napi_ok) 620 | return failed_ret; 621 | 622 | if (napi_create_function(env, nullptr, 0, SetOverlayAutohide, nullptr, &fn) != napi_ok) 623 | return failed_ret; 624 | if (napi_set_named_property(env, exports, "setAutohide", fn) != napi_ok) 625 | return failed_ret; 626 | 627 | if (napi_create_function(env, nullptr, 0, RemoveOverlay, nullptr, &fn) != napi_ok) 628 | return failed_ret; 629 | if (napi_set_named_property(env, exports, "remove", fn) != napi_ok) 630 | return failed_ret; 631 | 632 | if (napi_create_function(env, nullptr, 0, SwitchToInteractive, nullptr, &fn) != napi_ok) 633 | return failed_ret; 634 | if (napi_set_named_property(env, exports, "switchInteractiveMode", fn) != napi_ok) 635 | return failed_ret; 636 | 637 | if (napi_create_function(env, nullptr, 0, SetKeyboardCallback, nullptr, &fn) != napi_ok) 638 | return failed_ret; 639 | if (napi_set_named_property(env, exports, "setKeyboardCallback", fn) != napi_ok) 640 | return failed_ret; 641 | 642 | if (napi_create_function(env, nullptr, 0, SetMouseCallback, nullptr, &fn) != napi_ok) 643 | return failed_ret; 644 | if (napi_set_named_property(env, exports, "setMouseCallback", fn) != napi_ok) 645 | return failed_ret; 646 | 647 | return exports; 648 | } 649 | 650 | NAPI_MODULE(NODE_GYP_MODULE_NAME, init) 651 | -------------------------------------------------------------------------------- /src/overlay_logging.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "overlay_logging.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | bool log_output_disabled = true; 25 | bool log_output_old_stored = false; 26 | 27 | namespace fs = std::filesystem; 28 | std::ofstream log_output_file; 29 | 30 | const std::string getTimeStamp() 31 | { 32 | const std::time_t t = std::time(nullptr); 33 | 34 | struct tm buf; 35 | localtime_s(&buf, &t); 36 | 37 | char mbstr[128]={0}; 38 | std::strftime(mbstr, sizeof(mbstr), "%Y%m%d:%H%M%S.", &buf); 39 | unsigned __int64 now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 40 | 41 | std::ostringstream ss; 42 | ss << mbstr << std::setw(3) << std::setfill('0') << now%1000; 43 | return ss.str(); 44 | } 45 | 46 | void logging_start(std::string log_path) 47 | { 48 | if (log_output_file.is_open()) 49 | { 50 | log_output_disabled = false; 51 | return; 52 | } 53 | 54 | if( log_path.size() != 0) 55 | { 56 | std::error_code ec; 57 | if( !log_output_old_stored ) 58 | { 59 | log_output_old_stored = true; 60 | try { 61 | fs::path log_file = log_path; 62 | fs::path log_file_old = log_file; 63 | log_file_old.replace_extension("log.old"); 64 | fs::remove(log_file_old, ec); 65 | fs::rename(log_file, log_file_old, ec); 66 | fs::remove(log_file, ec); 67 | } catch (...) { 68 | } 69 | } 70 | try { 71 | log_output_file.open( log_path, std::ios_base::out | std::ios_base::app); 72 | log_output_disabled = false; 73 | } catch (...) { 74 | log_output_disabled = true; 75 | } 76 | } else { 77 | log_output_disabled = true; 78 | } 79 | } 80 | 81 | void logging_end() 82 | { 83 | if( log_output_file.is_open() ) 84 | { 85 | log_output_file.flush(); 86 | log_output_disabled = true; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/overlay_paint_frame.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "overlay_paint_frame.h" 16 | #include "overlay_paint_frame_js.h" 17 | #include "overlay_logging.h" 18 | 19 | #include 20 | 21 | overlay_frame::overlay_frame(overlay_frame_js * set_data) :data( set_data) 22 | { 23 | } 24 | 25 | overlay_frame::~overlay_frame() 26 | { 27 | data->clean(); 28 | delete data; 29 | data = nullptr; 30 | } 31 | 32 | void overlay_frame::get_array( void ** array_ref, size_t * array_size) 33 | { 34 | if(data != nullptr) 35 | { 36 | data->get_array(array_ref, array_size); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/overlay_paint_frame_js.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "overlay_paint_frame_js.h" 16 | #include "overlay_logging.h" 17 | #include 18 | 19 | 20 | overlay_frame_js::overlay_frame_js(napi_env env, napi_value array) 21 | { 22 | env_ref = env; 23 | napi_create_reference(env, array, 1, &array_cache_ref); 24 | } 25 | 26 | void overlay_frame_js::get_array( void ** array_ref, size_t * array_size) 27 | { 28 | napi_status status = napi_ok; 29 | napi_value buffer_cache = nullptr;; 30 | try { 31 | if( napi_get_reference_value(env_ref, array_cache_ref, &buffer_cache) == napi_ok) 32 | { 33 | status = napi_get_buffer_info(env_ref, buffer_cache, array_ref, array_size) ; 34 | } 35 | } catch (...) { 36 | } 37 | 38 | return; 39 | } 40 | 41 | void overlay_frame_js::clean() 42 | { 43 | napi_delete_reference(env_ref, array_cache_ref); 44 | } -------------------------------------------------------------------------------- /src/sl_overlay_api.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "sl_overlay_api.h" 16 | 17 | #include "overlay_logging.h" 18 | #include "sl_overlay_window.h" 19 | #include "sl_overlays.h" 20 | #include "sl_overlays_settings.h" 21 | 22 | extern HANDLE overlays_thread; 23 | extern DWORD overlays_thread_id; 24 | extern std::mutex thread_state_mutex; 25 | extern sl_overlay_thread_state thread_state; 26 | 27 | //==== node api ==== 28 | //when used as a "plugin" we have to start our own thread to work with windows events loop 29 | int WINAPI start_overlays_thread() 30 | { 31 | thread_state_mutex.lock(); 32 | if (thread_state != sl_overlay_thread_state::destoyed) 33 | { 34 | thread_state_mutex.unlock(); 35 | return 0; 36 | } else 37 | { 38 | thread_state = sl_overlay_thread_state::starting; 39 | thread_state_mutex.unlock(); 40 | 41 | overlays_thread = CreateThread(nullptr, 0, overlay_thread_func, nullptr, 0, &overlays_thread_id); 42 | if (overlays_thread) 43 | { 44 | return 1; 45 | } else 46 | { 47 | thread_state_mutex.lock(); 48 | thread_state = sl_overlay_thread_state::destoyed; 49 | thread_state_mutex.unlock(); 50 | return 0; 51 | } 52 | } 53 | } 54 | 55 | int WINAPI stop_overlays_thread() 56 | { 57 | thread_state_mutex.lock(); 58 | if (thread_state != sl_overlay_thread_state::runing) 59 | { 60 | thread_state_mutex.unlock(); 61 | return 0; 62 | } else 63 | { 64 | thread_state = sl_overlay_thread_state::stopping; 65 | thread_state_mutex.unlock(); 66 | 67 | BOOL ret = PostThreadMessage((DWORD)overlays_thread_id, WM_SLO_OVERLAY_COMMAND, COMMAND_QUIT, 0); 68 | 69 | if (ret) 70 | { 71 | return 1; 72 | } else 73 | { 74 | return 0; 75 | } 76 | } 77 | } 78 | 79 | std::string get_thread_status_name() 80 | { 81 | std::lock_guard lock(thread_state_mutex); 82 | std::string ret = "nop"; 83 | switch (thread_state) 84 | { 85 | case sl_overlay_thread_state::starting: 86 | ret = "starting"; 87 | break; 88 | case sl_overlay_thread_state::runing: 89 | ret = "runing"; 90 | break; 91 | case sl_overlay_thread_state::stopping: 92 | ret = "stopping"; 93 | break; 94 | case sl_overlay_thread_state::destoyed: 95 | ret = "destoyed"; 96 | break; 97 | } 98 | return ret; 99 | } 100 | 101 | int WINAPI get_overlays_count() 102 | { 103 | thread_state_mutex.lock(); 104 | if (thread_state != sl_overlay_thread_state::runing) 105 | { 106 | thread_state_mutex.unlock(); 107 | return 0; 108 | } else 109 | { 110 | int ret = smg_overlays::get_instance()->get_count(); 111 | 112 | thread_state_mutex.unlock(); 113 | return ret; 114 | } 115 | } 116 | 117 | int WINAPI show_overlays() 118 | { 119 | thread_state_mutex.lock(); 120 | if (thread_state != sl_overlay_thread_state::runing) 121 | { 122 | thread_state_mutex.unlock(); 123 | return 0; 124 | } else 125 | { 126 | BOOL ret = PostThreadMessage((DWORD)overlays_thread_id, WM_SLO_OVERLAY_COMMAND, COMMAND_SHOW_OVERLAYS, 0); 127 | 128 | thread_state_mutex.unlock(); 129 | return ret; 130 | } 131 | } 132 | 133 | int WINAPI hide_overlays() 134 | { 135 | thread_state_mutex.lock(); 136 | if (thread_state != sl_overlay_thread_state::runing) 137 | { 138 | thread_state_mutex.unlock(); 139 | return 0; 140 | } else 141 | { 142 | BOOL ret = PostThreadMessage((DWORD)overlays_thread_id, WM_SLO_OVERLAY_COMMAND, COMMAND_HIDE_OVERLAYS, 0); 143 | 144 | thread_state_mutex.unlock(); 145 | return ret; 146 | } 147 | } 148 | 149 | bool WINAPI is_overlays_hidden() 150 | { 151 | return !smg_overlays::get_instance()->showing_overlays; 152 | } 153 | 154 | int WINAPI remove_overlay(int id) 155 | { 156 | thread_state_mutex.lock(); 157 | if (thread_state != sl_overlay_thread_state::runing) 158 | { 159 | thread_state_mutex.unlock(); 160 | return 0; 161 | } else 162 | { 163 | BOOL ret = PostThreadMessage(overlays_thread_id, WM_SLO_OVERLAY_CLOSE, id, NULL); 164 | 165 | thread_state_mutex.unlock(); 166 | return ret; 167 | } 168 | } 169 | 170 | static int (*callback_keyboard_ptr)(WPARAM, LPARAM) = nullptr; 171 | static int (*callback_mouse_ptr)(WPARAM, LPARAM) = nullptr; 172 | static int (*callback_switch_ptr)() = nullptr; 173 | 174 | int WINAPI set_callback_for_keyboard_input(int (*ptr)(WPARAM, LPARAM)) 175 | { 176 | callback_keyboard_ptr = ptr; 177 | 178 | return 0; 179 | } 180 | 181 | int WINAPI set_callback_for_mouse_input(int (*ptr)(WPARAM, LPARAM)) 182 | { 183 | callback_mouse_ptr = ptr; 184 | 185 | return 0; 186 | } 187 | 188 | int WINAPI set_callback_for_switching_input(int (*ptr)()) 189 | { 190 | callback_switch_ptr = ptr; 191 | 192 | return 0; 193 | } 194 | 195 | int WINAPI use_callback_for_keyboard_input(WPARAM wParam, LPARAM lParam) 196 | { 197 | if (callback_keyboard_ptr != nullptr) 198 | { 199 | callback_keyboard_ptr(wParam, lParam); 200 | } 201 | return 0; 202 | } 203 | 204 | int WINAPI use_callback_for_mouse_input(WPARAM wParam, LPARAM lParam) 205 | { 206 | if (callback_mouse_ptr != nullptr) 207 | { 208 | callback_mouse_ptr(wParam, lParam); 209 | } 210 | return 0; 211 | } 212 | 213 | int WINAPI use_callback_for_switching_input() 214 | { 215 | if (callback_switch_ptr != nullptr) 216 | { 217 | callback_switch_ptr(); 218 | } 219 | return 0; 220 | } 221 | 222 | int WINAPI switch_overlays_user_input(bool mode_active) 223 | { 224 | BOOL ret = false; 225 | 226 | if (mode_active) 227 | { 228 | ret = PostThreadMessage((DWORD)overlays_thread_id, WM_SLO_OVERLAY_COMMAND, COMMAND_TAKE_INPUT, 0); 229 | } else 230 | { 231 | ret = PostThreadMessage((DWORD)overlays_thread_id, WM_SLO_OVERLAY_COMMAND, COMMAND_RELEASE_INPUT, 0); 232 | } 233 | 234 | return 0; 235 | } 236 | 237 | int WINAPI add_overlay_by_hwnd(const void* hwnd_array, size_t array_size) 238 | { 239 | int ret = -1; 240 | if (hwnd_array != nullptr && array_size == sizeof(HWND)) 241 | { 242 | thread_state_mutex.lock(); 243 | if (thread_state != sl_overlay_thread_state::runing) 244 | { 245 | thread_state_mutex.unlock(); 246 | } else 247 | { 248 | HWND hwnd; 249 | memcpy(&hwnd, hwnd_array, sizeof(HWND)); 250 | 251 | ret = smg_overlays::get_instance()->create_overlay_window_by_hwnd(hwnd); 252 | 253 | thread_state_mutex.unlock(); 254 | } 255 | } 256 | return ret; 257 | } 258 | 259 | int WINAPI paint_overlay_cached_buffer(int overlay_id, std::shared_ptr frame, int width, int height) 260 | { 261 | int ret = -1; 262 | { 263 | thread_state_mutex.lock(); 264 | if (thread_state != sl_overlay_thread_state::runing) 265 | { 266 | thread_state_mutex.unlock(); 267 | } else 268 | { 269 | std::shared_ptr overlay = smg_overlays::get_instance()->get_overlay_by_id(overlay_id); 270 | 271 | if (overlay != nullptr && width != 0 && height != 0) 272 | { 273 | RECT overlay_rect = overlay->get_rect(); 274 | 275 | if (width == overlay_rect.right - overlay_rect.left && height == overlay_rect.bottom - overlay_rect.top) 276 | { 277 | if (smg_overlays::get_instance()->showing_overlays) 278 | { 279 | if (overlay->set_cached_image(frame)) 280 | ret = 1; 281 | } 282 | } else 283 | { 284 | log_debug << "APP: paint_overlay_cached_buffer " << overlay_id << ", size " << width << "x" << height 285 | << ", for " << overlay_rect.right - overlay_rect.left << "x" 286 | << overlay_rect.bottom - overlay_rect.top << ", at [" << overlay_rect.left << ":"<< overlay_rect.top<< "]"<< std::endl; 287 | 288 | overlay_rect.right = overlay_rect.left + width; 289 | overlay_rect.bottom = overlay_rect.top + height; 290 | 291 | overlay->apply_new_rect(overlay_rect); 292 | 293 | ret = 0; 294 | } 295 | } 296 | 297 | thread_state_mutex.unlock(); 298 | } 299 | } 300 | return ret; 301 | } 302 | 303 | int WINAPI paint_overlay_from_buffer(int overlay_id, const void* image_array, size_t array_size, int width, int height) 304 | { 305 | int ret = -1; 306 | { 307 | thread_state_mutex.lock(); 308 | if (thread_state != sl_overlay_thread_state::runing) 309 | { 310 | thread_state_mutex.unlock(); 311 | } else 312 | { 313 | if (smg_overlays::get_instance()->showing_overlays) 314 | { 315 | std::shared_ptr overlay = smg_overlays::get_instance()->get_overlay_by_id(overlay_id); 316 | RECT overlay_rect = overlay->get_rect(); 317 | 318 | if (overlay != nullptr && width == overlay_rect.right - overlay_rect.left && 319 | height == overlay_rect.bottom - overlay_rect.top) 320 | { 321 | overlay->apply_image_from_buffer(image_array, array_size, width, height); 322 | } 323 | } 324 | thread_state_mutex.unlock(); 325 | } 326 | } 327 | return ret; 328 | } 329 | 330 | int WINAPI set_overlay_position(int id, int x, int y, int width, int height) 331 | { 332 | thread_state_mutex.lock(); 333 | if (thread_state != sl_overlay_thread_state::runing) 334 | { 335 | thread_state_mutex.unlock(); 336 | 337 | return -1; 338 | } else 339 | { 340 | RECT* n = new RECT; 341 | n->left = x; 342 | n->top = y; 343 | n->right = x + width; 344 | n->bottom = y + height; 345 | 346 | BOOL ret = PostThreadMessage(overlays_thread_id, WM_SLO_OVERLAY_POSITION, id, reinterpret_cast(n)); 347 | thread_state_mutex.unlock(); 348 | 349 | if (!ret) 350 | { 351 | delete n; 352 | return -1; 353 | } 354 | 355 | return id; 356 | } 357 | } 358 | 359 | int WINAPI set_overlay_transparency(int id, int transparency) 360 | { 361 | thread_state_mutex.lock(); 362 | if (thread_state != sl_overlay_thread_state::runing) 363 | { 364 | thread_state_mutex.unlock(); 365 | return -1; 366 | } else 367 | { 368 | if (transparency < 0 || transparency > 255) 369 | { 370 | transparency = 0; 371 | } 372 | BOOL ret = PostThreadMessage(overlays_thread_id, WM_SLO_OVERLAY_TRANSPARENCY, id, (LPARAM)(transparency)); 373 | thread_state_mutex.unlock(); 374 | 375 | if (!ret) 376 | { 377 | return -1; 378 | } 379 | 380 | return id; 381 | } 382 | 383 | return id; 384 | } 385 | 386 | int WINAPI set_overlay_visibility(int id, bool visibility) 387 | { 388 | thread_state_mutex.lock(); 389 | if (thread_state != sl_overlay_thread_state::runing) 390 | { 391 | thread_state_mutex.unlock(); 392 | return -1; 393 | } else 394 | { 395 | BOOL ret = PostThreadMessage(overlays_thread_id, WM_SLO_OVERLAY_VISIBILITY, id, (LPARAM)(visibility)); 396 | thread_state_mutex.unlock(); 397 | 398 | if (!ret) 399 | { 400 | return -1; 401 | } 402 | 403 | return id; 404 | } 405 | 406 | return id; 407 | } 408 | 409 | int WINAPI set_overlay_autohide(int id, int autohide_timeout, int autohide_transparency) 410 | { 411 | thread_state_mutex.lock(); 412 | if (thread_state != sl_overlay_thread_state::runing) 413 | { 414 | thread_state_mutex.unlock(); 415 | return -1; 416 | } else 417 | { 418 | if (autohide_timeout < 0) 419 | { 420 | autohide_timeout = 0; 421 | } 422 | 423 | if (autohide_transparency > 255 || autohide_transparency < 0) 424 | { 425 | autohide_transparency = 0; 426 | } 427 | DWORD autohide_params = (autohide_timeout << 10) + autohide_transparency; 428 | BOOL ret = PostThreadMessage(overlays_thread_id, WM_SLO_OVERLAY_SET_AUTOHIDE, id, (LPARAM)(autohide_params)); 429 | thread_state_mutex.unlock(); 430 | 431 | if (!ret) 432 | { 433 | return -1; 434 | } 435 | 436 | return id; 437 | } 438 | 439 | return id; 440 | } 441 | 442 | std::shared_ptr get_overlays() 443 | { 444 | thread_state_mutex.lock(); 445 | if (thread_state != sl_overlay_thread_state::runing) 446 | { 447 | thread_state_mutex.unlock(); 448 | return nullptr; 449 | } else 450 | { 451 | thread_state_mutex.unlock(); 452 | 453 | return smg_overlays::get_instance(); 454 | } 455 | } -------------------------------------------------------------------------------- /src/sl_overlay_window.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "sl_overlay_window.h" 16 | 17 | #include "sl_overlays_settings.h" 18 | #include "stdafx.h" 19 | 20 | #include 21 | #include "overlay_logging.h" 22 | 23 | void overlay_window::set_transparency(int transparency, bool save_as_normal) 24 | { 25 | if (overlay_hwnd != 0) 26 | { 27 | if (save_as_normal) 28 | { 29 | overlay_transparency = transparency; 30 | } 31 | SetLayeredWindowAttributes(overlay_hwnd, RGB(0xFF, 0xFF, 0xFF), transparency, LWA_ALPHA); 32 | } 33 | } 34 | 35 | void overlay_window::set_visibility(bool visibility, bool overlays_shown) 36 | { 37 | if (overlay_hwnd != 0) 38 | { 39 | overlay_visibility = visibility; 40 | if (IsWindowVisible(overlay_hwnd)) 41 | { 42 | if (!overlay_visibility) 43 | { 44 | ShowWindow(overlay_hwnd, SW_HIDE); 45 | } 46 | } else 47 | { 48 | if (overlay_visibility && overlays_shown) 49 | { 50 | ShowWindow(overlay_hwnd, SW_SHOW); 51 | } 52 | } 53 | } 54 | } 55 | 56 | int overlay_window::get_transparency() 57 | { 58 | return overlay_transparency; 59 | } 60 | 61 | void overlay_window::set_autohide(int timeout, int transparency) 62 | { 63 | autohide_after = timeout; 64 | autohide_by_transparency = transparency; 65 | content_updated = true; 66 | reset_autohide(); 67 | } 68 | 69 | bool overlay_window::ready_to_create_overlay() 70 | { 71 | return orig_handle != nullptr; 72 | } 73 | 74 | bool overlay_window::is_content_updated() 75 | { 76 | return content_updated; 77 | } 78 | 79 | bool overlay_window::is_visible() 80 | { 81 | return overlay_visibility; 82 | } 83 | 84 | void overlay_window::apply_interactive_mode(bool is_intercepting) 85 | { 86 | if (overlay_hwnd != 0) 87 | { 88 | if (is_intercepting) 89 | { 90 | set_transparency(255, false); 91 | } else 92 | { 93 | set_transparency(get_transparency(), false); 94 | } 95 | } 96 | } 97 | 98 | void overlay_window::check_autohide() 99 | { 100 | ULONGLONG current_ticks = GetTickCount64(); 101 | 102 | if (autohide_after > 0 && !autohidden) 103 | { 104 | if (current_ticks > (last_content_chage_ticks + 1000 * autohide_after)) 105 | { 106 | autohidden = true; 107 | if (autohide_by_transparency > 0) 108 | { 109 | set_transparency(autohide_by_transparency, false); 110 | } else 111 | { 112 | ShowWindow(overlay_hwnd, SW_HIDE); 113 | } 114 | } 115 | } 116 | } 117 | 118 | void overlay_window::reset_autohide_timer() 119 | { 120 | if (autohidden) 121 | { 122 | if (autohide_by_transparency > 0) 123 | { 124 | set_transparency(overlay_transparency, false); 125 | } 126 | autohidden = false; 127 | } 128 | last_content_chage_ticks = GetTickCount64(); 129 | } 130 | 131 | overlay_window::~overlay_window() 132 | { 133 | clean_resources(); 134 | } 135 | 136 | overlay_window::overlay_window() 137 | { 138 | last_content_chage_ticks = 0; 139 | overlay_visibility = true; 140 | content_updated = false; 141 | content_set = false; 142 | static int id_counter = 128; 143 | id = id_counter++; 144 | orig_handle = nullptr; 145 | overlay_hwnd = nullptr; 146 | manual_position = false; 147 | status = overlay_status::creating; 148 | rect = {0}; 149 | 150 | overlay_transparency = -1; 151 | 152 | autohide_after = 0; 153 | autohidden = false; 154 | autohide_by_transparency = 50; 155 | } 156 | 157 | overlay_window_gdi::overlay_window_gdi() 158 | { 159 | hdc = nullptr; 160 | hbmp = nullptr; 161 | } 162 | 163 | overlay_window_direct2d::overlay_window_direct2d() 164 | { 165 | m_pRenderTarget = nullptr; 166 | m_pBitmap = nullptr; 167 | } 168 | 169 | void overlay_window::clean_resources() 170 | { 171 | if (status != overlay_status::destroing) 172 | { 173 | status = overlay_status::destroing; 174 | log_info << "APP: clean_resources for " << id << std::endl; 175 | 176 | if (overlay_hwnd != nullptr) 177 | { 178 | log_info << "APP: clean_resources close overlay window hwnd " << overlay_hwnd << std::endl; 179 | DestroyWindow(overlay_hwnd); 180 | } else 181 | { 182 | PostMessage(0, WM_SLO_OVERLAY_WINDOW_DESTOYED, id, NULL); 183 | } 184 | } 185 | } 186 | 187 | void overlay_window_gdi::clean_resources() 188 | { 189 | if (status != overlay_status::destroing) 190 | { 191 | if (hdc != nullptr) 192 | { 193 | DeleteDC(hdc); 194 | hdc = nullptr; 195 | } 196 | 197 | if (hbmp != nullptr) 198 | { 199 | DeleteObject(hbmp); 200 | hbmp = nullptr; 201 | } 202 | } 203 | 204 | overlay_window::clean_resources(); 205 | } 206 | 207 | void overlay_window_direct2d::clean_resources() 208 | { 209 | if (status != overlay_status::destroing) 210 | { 211 | if (m_pRenderTarget != nullptr) 212 | { 213 | m_pRenderTarget->Release(); 214 | m_pRenderTarget = nullptr; 215 | } 216 | } 217 | 218 | overlay_window::clean_resources(); 219 | } 220 | 221 | RECT overlay_window::get_rect() 222 | { 223 | std::lock_guard lock(rect_access); 224 | RECT ret = rect; 225 | return ret; 226 | } 227 | 228 | bool overlay_window::apply_new_rect(RECT& new_rect) 229 | { 230 | manual_position = true; 231 | 232 | if (orig_handle) 233 | { 234 | int iDpi = try_to_get_dpi(orig_handle); 235 | int dpiScaledX = MulDiv(new_rect.left, iDpi, 96); 236 | int dpiScaledY = MulDiv(new_rect.top, iDpi, 96); 237 | 238 | log_debug << "APP: apply_new_rect " << new_rect.left << " to " << dpiScaledX << std::endl; 239 | 240 | SetWindowPos(overlay_hwnd, HWND_TOPMOST, dpiScaledX, dpiScaledY, new_rect.right - new_rect.left, new_rect.bottom - new_rect.top, SWP_NOREDRAW); 241 | } 242 | 243 | return set_rect(new_rect); 244 | } 245 | 246 | bool overlay_window::set_new_position(int x, int y) 247 | { 248 | RECT ret = get_rect(); 249 | 250 | int shift = ret.left - x; 251 | ret.left -= shift; 252 | ret.right -= shift; 253 | 254 | shift = ret.top - y; 255 | ret.top -= shift; 256 | ret.bottom -= shift; 257 | 258 | manual_position = true; 259 | 260 | if (overlay_hwnd) 261 | { 262 | SetWindowPos(overlay_hwnd, HWND_TOPMOST, x, y, ret.right - ret.left, ret.bottom - ret.top, SWP_NOREDRAW); 263 | } 264 | 265 | return set_rect(ret); 266 | } 267 | 268 | bool overlay_window::set_rect(RECT& new_rect) 269 | { 270 | std::lock_guard lock(rect_access); 271 | rect = new_rect; 272 | return true; 273 | } 274 | 275 | bool overlay_window::set_cached_image(std::shared_ptr save_frame) 276 | { 277 | { 278 | std::lock_guard lock(frame_access); 279 | frame = save_frame; 280 | 281 | const RECT overlay_rect = get_rect(); 282 | void* image_array = nullptr; 283 | size_t image_array_size = 0; 284 | size_t expected_array_size = (overlay_rect.right - overlay_rect.left) * (overlay_rect.bottom - overlay_rect.top) * 4; 285 | 286 | frame->get_array(&image_array, &image_array_size); 287 | if (image_array_size != expected_array_size) 288 | { 289 | log_error << "APP: Saving image from electron array_size = " << image_array_size << ", expected = " << expected_array_size << std::endl; 290 | frame = nullptr; 291 | return false; 292 | } else 293 | { 294 | if (apply_image_from_buffer(image_array, image_array_size, overlay_rect.right - overlay_rect.left, overlay_rect.bottom - overlay_rect.top)) 295 | { 296 | content_updated = true; 297 | } 298 | frame = nullptr; 299 | } 300 | } 301 | 302 | reset_autohide(); 303 | 304 | return true; 305 | } 306 | 307 | bool overlay_window::reset_autohide() 308 | { 309 | if (autohidden) 310 | { 311 | if (!IsWindowVisible(overlay_hwnd)) 312 | { 313 | ShowWindow(overlay_hwnd, SW_SHOW); 314 | } 315 | 316 | if (autohide_by_transparency > 0) 317 | { 318 | set_transparency(overlay_transparency, false); 319 | } 320 | 321 | autohidden = false; 322 | } 323 | return true; 324 | } 325 | bool overlay_window_gdi::apply_image_from_buffer(const void* image_array, size_t array_size, int width, int height) 326 | { 327 | log_debug << "APP: Saving image from electron array_size = " << array_size << ", w " << width << ", h " << height << std::endl; 328 | bool ret = true; 329 | if (hbmp != nullptr) 330 | { 331 | LONG workedout = 0; 332 | 333 | BITMAPINFO phmi; 334 | phmi.bmiHeader.biSize = sizeof(phmi.bmiHeader); 335 | phmi.bmiHeader.biWidth = width; 336 | phmi.bmiHeader.biHeight = -height; 337 | phmi.bmiHeader.biPlanes = 1; 338 | phmi.bmiHeader.biBitCount = 32; 339 | phmi.bmiHeader.biCompression = BI_RGB; 340 | workedout = SetDIBitsToDevice(hdc, 0, 0, width, height, 0, 0, 0, height, image_array, &phmi, false); 341 | if (workedout != height) 342 | { 343 | log_error << "APP: Saving image from electron with SetDIBitsToDevice failed with workedout = " << workedout << std::endl; 344 | ret = false; 345 | } else 346 | { 347 | content_set = true; 348 | } 349 | } else 350 | { 351 | log_error << "APP: Saving image from electron failed. no hbmp to save to." << std::endl; 352 | } 353 | 354 | return ret; 355 | } 356 | 357 | bool overlay_window_direct2d::apply_image_from_buffer(const void* image_array, size_t array_size, int width, int height) 358 | { 359 | log_debug << "APP: Saving image from electron array_size = " << array_size << ", w " << width << ", h " << height << std::endl; 360 | bool ret = true; 361 | 362 | if (m_pBitmap != nullptr) 363 | { 364 | D2D1_RECT_U bits_size = {0, 0, (uint32_t)width, (uint32_t)height}; 365 | m_pBitmap->CopyFromMemory(&bits_size, image_array, width * 4); 366 | content_set = true; 367 | } 368 | 369 | return ret; 370 | } 371 | 372 | void overlay_window_direct2d::create_render_target(ID2D1Factory* m_pDirect2dFactory) 373 | { 374 | if (!m_pRenderTarget) 375 | { 376 | HRESULT hr = S_OK; 377 | RECT rc; 378 | GetClientRect(overlay_hwnd, &rc); 379 | 380 | D2D1_SIZE_U size = D2D1::SizeU(rc.right - rc.left, rc.bottom - rc.top); 381 | 382 | // Create a Direct2D render target. 383 | hr = m_pDirect2dFactory->CreateHwndRenderTarget( 384 | D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED)), 385 | D2D1::HwndRenderTargetProperties(overlay_hwnd, size), 386 | &m_pRenderTarget); 387 | } 388 | } 389 | 390 | std::string overlay_window_direct2d::get_status() 391 | { 392 | if (!overlay_hwnd) 393 | { 394 | return "no_overlay_window"; 395 | } else if (!m_pRenderTarget || !m_pBitmap) 396 | { 397 | return "failed_drawing"; 398 | } 399 | return "ok"; 400 | } 401 | 402 | std::string overlay_window_gdi::get_status() 403 | { 404 | if (!overlay_hwnd) 405 | { 406 | return "no_overlay_window"; 407 | } else if (!hdc) 408 | { 409 | return "failed_drawing"; 410 | } 411 | return "ok"; 412 | } 413 | 414 | void overlay_window_gdi::set_dbl_buffering(bool enable) 415 | { 416 | g_bDblBuffered = enable; 417 | } 418 | 419 | void overlay_window_gdi::paint_to_window(HDC window_hdc) 420 | { 421 | const RECT overlay_rect = get_rect(); 422 | BOOL ret = true; 423 | PAINTSTRUCT ps; 424 | HPAINTBUFFER hBufferedPaint = nullptr; 425 | RECT rc; 426 | 427 | window_hdc = hdc; 428 | 429 | GetClientRect(overlay_hwnd, &rc); 430 | HDC display_hdc = BeginPaint(overlay_hwnd, &ps); 431 | 432 | if (g_bDblBuffered) 433 | { 434 | HDC hdcMem; 435 | hBufferedPaint = BeginBufferedPaint(display_hdc, &rc, BPBF_COMPOSITED, nullptr, &hdcMem); 436 | if (hBufferedPaint) 437 | { 438 | display_hdc = hdcMem; 439 | } 440 | } 441 | 442 | ret = BitBlt(window_hdc, 0, 0, overlay_rect.right - overlay_rect.left, overlay_rect.bottom - overlay_rect.top, display_hdc, 0, 0, SRCCOPY); 443 | 444 | if (!ret) 445 | { 446 | log_error << "APP: paint_to_window had issue " << GetLastError() << std::endl; 447 | } 448 | 449 | if (hBufferedPaint) 450 | { 451 | BufferedPaintMakeOpaque(hBufferedPaint, nullptr); 452 | EndBufferedPaint(hBufferedPaint, TRUE); 453 | } 454 | 455 | EndPaint(overlay_hwnd, &ps); 456 | 457 | ValidateRect(overlay_hwnd, &rect); 458 | 459 | last_content_chage_ticks = GetTickCount64(); 460 | 461 | content_updated = false; 462 | } 463 | 464 | void overlay_window_direct2d::paint_to_window(HDC window_hdc) 465 | { 466 | PAINTSTRUCT ps; 467 | HDC hdc = BeginPaint(overlay_hwnd, &ps); 468 | 469 | if (m_pRenderTarget) 470 | { 471 | HRESULT hr = S_OK; 472 | 473 | m_pRenderTarget->BeginDraw(); 474 | 475 | m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); 476 | 477 | D2D1_SIZE_F rtSize = m_pRenderTarget->GetSize(); 478 | 479 | // Draw a grid background. 480 | int width = static_cast(rtSize.width); 481 | int height = static_cast(rtSize.height); 482 | 483 | if (m_pBitmap != nullptr && content_set) 484 | { 485 | m_pRenderTarget->DrawBitmap(m_pBitmap, D2D1::RectF(0, 0, width, height), 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, nullptr); 486 | } else 487 | { 488 | m_pRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f)); 489 | } 490 | 491 | hr = m_pRenderTarget->EndDraw(); 492 | } 493 | 494 | EndPaint(overlay_hwnd, &ps); 495 | 496 | ValidateRect(overlay_hwnd, &rect); 497 | 498 | last_content_chage_ticks = GetTickCount64(); 499 | 500 | content_updated = false; 501 | } 502 | 503 | bool overlay_window::apply_size_from_orig() 504 | { 505 | BOOL ret = false; 506 | 507 | ret = GetWindowRect(orig_handle, &rect); 508 | 509 | return ret; 510 | } 511 | 512 | bool overlay_window::create_window() 513 | { 514 | if (overlay_hwnd == nullptr && ready_to_create_overlay()) 515 | { 516 | DWORD const dwStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; // no border or title bar 517 | DWORD const dwStyleEx = WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | 0x00000800; 518 | //| 0x40000000 519 | //| 0x80000000 520 | //| 0x20000000; 521 | // transparent, topmost, with no taskbar 522 | 523 | overlay_hwnd = CreateWindowEx(dwStyleEx, g_szWindowClass, NULL, dwStyle, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL); 524 | 525 | if (overlay_hwnd) 526 | { 527 | if (app_settings->use_color_key) 528 | { 529 | SetLayeredWindowAttributes(overlay_hwnd, RGB(0xFF, 0xFF, 0xFF), 0xD0, LWA_COLORKEY); 530 | } else 531 | { 532 | set_transparency(app_settings->transparency); 533 | } 534 | 535 | SetWindowPos(overlay_hwnd, HWND_TOPMOST, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOREDRAW); 536 | return true; 537 | } 538 | } 539 | return false; 540 | } 541 | 542 | bool overlay_window_gdi::create_window_content_buffer() 543 | { 544 | bool created = false; 545 | RECT client_rect = {0}; 546 | GetWindowRect(overlay_hwnd, &client_rect); 547 | 548 | HDC hdcScreen = GetDC(overlay_hwnd); 549 | 550 | if (hdcScreen != nullptr) 551 | { 552 | int new_x = client_rect.left; 553 | int new_y = client_rect.top; 554 | int new_width = client_rect.right - client_rect.left; 555 | int new_height = client_rect.bottom - client_rect.top; 556 | 557 | log_info << "APP: create_window_content_buffer rect at [" << new_x << " , " << new_y << "]" << std::endl; 558 | 559 | HDC new_hdc = nullptr; 560 | HBITMAP new_hbmp = nullptr; 561 | 562 | new_hdc = CreateCompatibleDC(hdcScreen); 563 | new_hbmp = CreateCompatibleBitmap(hdcScreen, new_width, new_height); 564 | SelectObject(new_hdc, new_hbmp); 565 | 566 | if (new_hdc == nullptr || new_hbmp == nullptr) 567 | { 568 | DeleteDC(new_hdc); 569 | DeleteObject(new_hbmp); 570 | } else 571 | { 572 | DeleteDC(hdc); 573 | DeleteObject(hbmp); 574 | 575 | hdc = new_hdc; 576 | hbmp = new_hbmp; 577 | created = true; 578 | } 579 | } else 580 | { 581 | log_error << "APP: create_window_content_buffer failed to get rect from orig window " << GetLastError() << std::endl; 582 | } 583 | content_set = false; 584 | 585 | ReleaseDC(nullptr, hdcScreen); 586 | 587 | return created; 588 | } 589 | bool overlay_window_direct2d::create_window_content_buffer() 590 | { 591 | bool created = false; 592 | RECT client_rect = {0}; 593 | GetWindowRect(overlay_hwnd, &client_rect); 594 | 595 | if (m_pRenderTarget) 596 | { 597 | HRESULT hr = S_OK; 598 | int new_width = client_rect.right - client_rect.left; 599 | int new_height = client_rect.bottom - client_rect.top; 600 | m_pRenderTarget->Resize(D2D1::SizeU(new_width, new_height)); 601 | 602 | log_debug << "APP: create_window_content_buffer d2d width " << new_width << ", height " << new_height << std::endl; 603 | if (m_pBitmap != nullptr) 604 | { 605 | m_pBitmap->Release(); 606 | m_pBitmap = nullptr; 607 | } 608 | 609 | D2D1_SIZE_U bitmap_size; 610 | bitmap_size.width = new_width; 611 | bitmap_size.height = new_height; 612 | 613 | float dpi_x, dpi_y; 614 | m_pRenderTarget->GetDpi(&dpi_x, &dpi_y); 615 | 616 | hr = m_pRenderTarget->CreateBitmap(bitmap_size, D2D1::BitmapProperties(D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED), dpi_x, dpi_y), &m_pBitmap); 617 | 618 | log_debug << "APP: create_window_content_buffer d2d hr " << hr << std::endl; 619 | if (SUCCEEDED(hr)) 620 | { 621 | created = true; 622 | } 623 | } 624 | 625 | content_set = false; 626 | 627 | return created; 628 | } -------------------------------------------------------------------------------- /src/sl_overlays.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "sl_overlays.h" 16 | #include "stdafx.h" 17 | 18 | #include 19 | #include 20 | 21 | #include "sl_overlay_window.h" 22 | #include "sl_overlays_settings.h" 23 | 24 | #include "overlay_logging.h" 25 | #include "sl_overlay_api.h" 26 | 27 | #pragma comment(lib, "uxtheme.lib") 28 | #pragma comment(lib, "shcore.lib") 29 | 30 | wchar_t const g_szWindowClass[] = L"overthetop_overlay"; 31 | 32 | std::shared_ptr smg_overlays::instance = nullptr; 33 | 34 | extern HANDLE overlays_thread; 35 | extern DWORD overlays_thread_id; 36 | extern std::mutex thread_state_mutex; 37 | extern sl_overlay_thread_state thread_state; 38 | 39 | LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 40 | 41 | bool smg_overlays::process_commands(MSG& msg) 42 | { 43 | bool ret = false; 44 | log_info << "APP: process_commands id " << msg.wParam << std::endl; 45 | switch (msg.wParam) 46 | { 47 | case COMMAND_SHOW_OVERLAYS: 48 | { 49 | if (showing_overlays) 50 | { 51 | // need to hide befor show. or show can be ignored. 52 | showing_overlays = false; 53 | hide_overlays(); 54 | } 55 | 56 | showup_overlays(); 57 | showing_overlays = true; 58 | ret = true; 59 | } 60 | break; 61 | case COMMAND_HIDE_OVERLAYS: 62 | { 63 | showing_overlays = false; 64 | 65 | hide_overlays(); 66 | ret = true; 67 | } 68 | break; 69 | case COMMAND_QUIT: 70 | { 71 | thread_state_mutex.lock(); 72 | 73 | log_info << "APP: COMMAND_QUIT " << static_cast(thread_state) << std::endl; 74 | if (thread_state != sl_overlay_thread_state::runing) 75 | { 76 | thread_state_mutex.unlock(); 77 | } else 78 | { 79 | thread_state = sl_overlay_thread_state::stopping; 80 | thread_state_mutex.unlock(); 81 | } 82 | 83 | if (!quiting) 84 | { 85 | quit(); 86 | } 87 | ret = true; 88 | } 89 | break; 90 | 91 | case COMMAND_TAKE_INPUT: 92 | { 93 | hook_user_input(); 94 | 95 | showup_overlays(); 96 | apply_interactive_mode_view(); 97 | } 98 | break; 99 | case COMMAND_RELEASE_INPUT: 100 | { 101 | unhook_user_input(); 102 | 103 | apply_interactive_mode_view(); 104 | } 105 | break; 106 | }; 107 | 108 | return ret; 109 | } 110 | 111 | void smg_overlays::quit() 112 | { 113 | log_info << "APP: quit " << std::endl; 114 | quiting = true; 115 | 116 | if (showing_windows.size() != 0) 117 | { 118 | std::for_each(showing_windows.begin(), showing_windows.end(), [](std::shared_ptr& n) { 119 | PostMessage(0, WM_SLO_OVERLAY_CLOSE, n->id, 0); 120 | }); 121 | } else 122 | { 123 | on_overlay_destroy(nullptr); 124 | } 125 | 126 | //it's not all. after last windows will be destroyed then thread quits 127 | } 128 | 129 | int smg_overlays::create_overlay_window_by_hwnd(HWND hwnd) 130 | { 131 | std::shared_ptr new_overlay_window; 132 | if (direct2d_paint) 133 | { 134 | new_overlay_window = std::make_shared(); 135 | } else 136 | { 137 | std::shared_ptr new_overlay_window_gdi = std::make_shared(); 138 | new_overlay_window_gdi->set_dbl_buffering(g_bDblBuffered); 139 | new_overlay_window = new_overlay_window_gdi; 140 | } 141 | 142 | new_overlay_window->orig_handle = hwnd; 143 | new_overlay_window->apply_size_from_orig(); 144 | 145 | { 146 | std::unique_lock lock(overlays_list_access); 147 | showing_windows.push_back(new_overlay_window); 148 | } 149 | 150 | PostThreadMessage( 151 | overlays_thread_id, 152 | WM_SLO_HWND_SOURCE_READY, 153 | new_overlay_window->id, 154 | reinterpret_cast(&(new_overlay_window->orig_handle))); 155 | 156 | return new_overlay_window->id; 157 | } 158 | 159 | void smg_overlays::on_update_timer() 160 | { 161 | if (showing_overlays) 162 | { 163 | std::shared_lock lock(overlays_list_access); 164 | std::for_each( 165 | showing_windows.begin(), 166 | showing_windows.end(), 167 | [is_intercepting = this->is_intercepting](std::shared_ptr& n) { 168 | if (n->is_visible()) 169 | { 170 | if (n->is_content_updated()) 171 | { 172 | InvalidateRect(n->overlay_hwnd, nullptr, TRUE); 173 | } else 174 | { 175 | if (!is_intercepting) 176 | { 177 | n->check_autohide(); 178 | } 179 | } 180 | } 181 | }); 182 | } 183 | } 184 | 185 | void smg_overlays::deinit() 186 | { 187 | if (g_bDblBuffered) 188 | BufferedPaintUnInit(); 189 | 190 | log_info << "APP: deinit " << std::endl; 191 | quiting = false; 192 | } 193 | 194 | void smg_overlays::showup_overlays() 195 | { 196 | log_info << "APP: showup_overlays " << std::endl; 197 | { 198 | std::shared_lock lock(overlays_list_access); 199 | std::for_each(showing_windows.begin(), showing_windows.end(), [](std::shared_ptr& n) { 200 | if (n->overlay_hwnd != 0 && n->is_visible()) 201 | { 202 | ShowWindow(n->overlay_hwnd, SW_SHOW); 203 | n->reset_autohide_timer(); 204 | } 205 | }); 206 | } 207 | } 208 | 209 | void smg_overlays::hide_overlays() 210 | { 211 | log_info << "APP: hide_overlays " << std::endl; 212 | std::shared_lock lock(overlays_list_access); 213 | std::for_each(showing_windows.begin(), showing_windows.end(), [](std::shared_ptr& n) { 214 | if (n->overlay_hwnd != 0) 215 | { 216 | ShowWindow(n->overlay_hwnd, SW_HIDE); 217 | } 218 | }); 219 | } 220 | 221 | void smg_overlays::apply_interactive_mode_view() 222 | { 223 | log_info << "APP: apply_interactive_mode_view " << std::endl; 224 | { 225 | std::shared_lock lock(overlays_list_access); 226 | std::for_each( 227 | showing_windows.begin(), 228 | showing_windows.end(), 229 | [is_intercepting = this->is_intercepting](std::shared_ptr& n) { 230 | n->apply_interactive_mode(is_intercepting); 231 | }); 232 | } 233 | } 234 | 235 | void smg_overlays::create_overlay_window_class() 236 | { 237 | WNDCLASSEX wcex = {sizeof(wcex)}; 238 | wcex.style = CS_HREDRAW | CS_VREDRAW; 239 | wcex.lpfnWndProc = WndProc; 240 | wcex.hInstance = GetModuleHandle(NULL); 241 | wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); 242 | wcex.hbrBackground = 0; 243 | wcex.lpszClassName = g_szWindowClass; 244 | 245 | RegisterClassEx(&wcex); 246 | } 247 | 248 | bool smg_overlays::is_inside_overlay(int x, int y) 249 | { 250 | bool ret = false; 251 | std::shared_lock lock(overlays_list_access); 252 | std::for_each(showing_windows.begin(), showing_windows.end(), [&ret, &x, &y](std::shared_ptr& n) { 253 | RECT o_rect = n->get_rect(); 254 | if (n->is_visible()) 255 | { 256 | if (x <= o_rect.right && x >= o_rect.left) 257 | { 258 | if (y <= o_rect.bottom && y >= o_rect.top) 259 | { 260 | ret = true; 261 | } 262 | } 263 | } 264 | }); 265 | return ret; 266 | } 267 | 268 | void smg_overlays::create_window_for_overlay(std::shared_ptr& overlay) 269 | { 270 | if (overlay->create_window()) 271 | { 272 | if (showing_overlays) 273 | { 274 | ShowWindow(overlay->overlay_hwnd, SW_SHOW); 275 | } else 276 | { 277 | ShowWindow(overlay->overlay_hwnd, SW_HIDE); 278 | } 279 | } 280 | } 281 | 282 | HHOOK msg_hook = nullptr; 283 | HHOOK llkeyboard_hook = nullptr; 284 | HHOOK llmouse_hook = nullptr; 285 | 286 | LRESULT CALLBACK CallWndMsgProc(int nCode, WPARAM wParam, LPARAM lParam) 287 | { 288 | log_info << "APP: CallWndMsgProc " << wParam << std::endl; 289 | 290 | return CallNextHookEx(msg_hook, nCode, wParam, lParam); 291 | } 292 | 293 | LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) 294 | { 295 | if (nCode >= 0) 296 | { 297 | KBDLLHOOKSTRUCT* event = (KBDLLHOOKSTRUCT*)lParam; 298 | log_info << "APP: LowLevelKeyboardProc " << event->vkCode << ", " << event->dwExtraInfo << std::endl; 299 | 300 | if (event->vkCode == VK_ESCAPE) 301 | { 302 | use_callback_for_switching_input(); 303 | } else 304 | 305 | use_callback_for_keyboard_input(wParam, lParam); 306 | return -1; 307 | } 308 | 309 | return CallNextHookEx(llkeyboard_hook, nCode, wParam, lParam); 310 | } 311 | 312 | LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) 313 | { 314 | if (nCode >= 0) 315 | { 316 | MSLLHOOKSTRUCT* event = (MSLLHOOKSTRUCT*)lParam; 317 | log_info << "APP: LowLevelMouseProc " << wParam << ", " << event->pt.x << ", " << event->pt.y << ", " 318 | << event->dwExtraInfo << std::endl; 319 | 320 | std::shared_ptr app = smg_overlays::get_instance(); 321 | if (app->is_inside_overlay(event->pt.x, event->pt.y)) 322 | { 323 | use_callback_for_mouse_input(wParam, lParam); 324 | } else 325 | { 326 | if (wParam != WM_MOUSEMOVE && wParam != WM_MOUSEWHEEL && wParam != WM_MOUSEHWHEEL) 327 | { 328 | use_callback_for_switching_input(); 329 | } 330 | } 331 | 332 | if (wParam != WM_MOUSEMOVE) 333 | { 334 | return -1; 335 | } 336 | } 337 | return CallNextHookEx(llmouse_hook, nCode, wParam, lParam); 338 | } 339 | 340 | void smg_overlays::hook_user_input() 341 | { 342 | log_info << "APP: hook_user_input " << std::endl; 343 | 344 | if (!is_intercepting) 345 | { 346 | HWND game_hwnd = GetForegroundWindow(); 347 | if (game_hwnd != nullptr) 348 | { 349 | //print window title 350 | TCHAR title[256]; 351 | GetWindowText(game_hwnd, title, 256); 352 | std::wstring title_wstr(title); 353 | std::string title_str(title_wstr.begin(), title_wstr.end()); 354 | log_debug << "APP: hook_user_input catch window - " << title_str << std::endl; 355 | } 356 | 357 | llkeyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0); 358 | llmouse_hook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, NULL, 0); 359 | 360 | is_intercepting = true; 361 | 362 | log_info << "APP: Input hooked" << std::endl; 363 | } 364 | } 365 | 366 | void smg_overlays::unhook_user_input() 367 | { 368 | log_info << "APP: unhook_user_input " << std::endl; 369 | if (is_intercepting) 370 | { 371 | if (msg_hook != nullptr) 372 | { 373 | UnhookWindowsHookEx(msg_hook); 374 | msg_hook = nullptr; 375 | } 376 | if (llkeyboard_hook != nullptr) 377 | { 378 | UnhookWindowsHookEx(llkeyboard_hook); 379 | llkeyboard_hook = nullptr; 380 | } 381 | if (llmouse_hook != nullptr) 382 | { 383 | UnhookWindowsHookEx(llmouse_hook); 384 | llmouse_hook = nullptr; 385 | } 386 | 387 | log_info << "APP: Input unhooked" << std::endl; 388 | is_intercepting = false; 389 | } 390 | } 391 | 392 | size_t smg_overlays::get_count() 393 | { 394 | std::shared_lock lock(overlays_list_access); 395 | return showing_windows.size(); 396 | } 397 | 398 | std::shared_ptr smg_overlays::get_overlay_by_id(int overlay_id) 399 | { 400 | std::shared_ptr ret; 401 | std::shared_lock lock(overlays_list_access); 402 | 403 | std::list>::iterator findIter = 404 | std::find_if(showing_windows.begin(), showing_windows.end(), [&overlay_id](std::shared_ptr& n) { 405 | return overlay_id == n->id; 406 | }); 407 | 408 | if (findIter != showing_windows.end()) 409 | { 410 | ret = *findIter; 411 | } 412 | 413 | return ret; 414 | } 415 | 416 | std::shared_ptr smg_overlays::get_overlay_by_window(HWND overlay_hwnd) 417 | { 418 | std::shared_ptr ret; 419 | std::shared_lock lock(overlays_list_access); 420 | 421 | std::list>::iterator findIter = 422 | std::find_if(showing_windows.begin(), showing_windows.end(), [&overlay_hwnd](std::shared_ptr& n) { 423 | return overlay_hwnd == n->overlay_hwnd; 424 | }); 425 | 426 | if (findIter != showing_windows.end()) 427 | { 428 | ret = *findIter; 429 | } 430 | 431 | return ret; 432 | } 433 | 434 | bool smg_overlays::remove_overlay(std::shared_ptr overlay) 435 | { 436 | log_info << "APP: RemoveOverlay status " << static_cast(overlay->status) << std::endl; 437 | if (overlay->status != overlay_status::destroing) 438 | { 439 | overlay->clean_resources(); 440 | 441 | return true; 442 | } 443 | return false; 444 | } 445 | 446 | bool smg_overlays::on_window_destroy(HWND window) 447 | { 448 | auto overlay = get_overlay_by_window(window); 449 | log_info << "APP: on_window_destroy and overlay found " << (overlay != nullptr) << std::endl; 450 | const bool removed = on_overlay_destroy(overlay); 451 | return removed; 452 | } 453 | 454 | bool smg_overlays::on_overlay_destroy(std::shared_ptr overlay) 455 | { 456 | bool removed = false; 457 | if (overlay != nullptr) 458 | { 459 | log_info << "APP: overlay status was " << static_cast(overlay->status) << std::endl; 460 | if (overlay->status == overlay_status::destroing) 461 | { 462 | std::unique_lock lock(overlays_list_access); 463 | showing_windows.remove_if([&overlay](std::shared_ptr& n) { return (overlay->id == n->id); }); 464 | removed = true; 465 | } 466 | } 467 | 468 | log_info << "APP: overlays count " << showing_windows.size() << " and quiting " << quiting << std::endl; 469 | if (showing_windows.size() == 0 && quiting) 470 | { 471 | PostQuitMessage(0); 472 | } 473 | 474 | return removed; 475 | } 476 | 477 | std::vector smg_overlays::get_ids() 478 | { 479 | std::vector ret; 480 | int i = 0; 481 | std::shared_lock lock(overlays_list_access); 482 | ret.resize(showing_windows.size()); 483 | std::for_each(showing_windows.begin(), showing_windows.end(), [&ret, &i](std::shared_ptr& n) { 484 | ret[i] = n->id; 485 | i++; 486 | }); 487 | 488 | return ret; 489 | } 490 | 491 | std::shared_ptr smg_overlays::get_instance() 492 | { 493 | if (instance == nullptr) 494 | { 495 | instance = std::make_shared(); 496 | } 497 | return instance; 498 | } 499 | 500 | smg_overlays::smg_overlays() 501 | { 502 | showing_overlays = false; 503 | quiting = false; 504 | 505 | log_info << "APP: start overlays " << std::endl; 506 | } 507 | 508 | smg_overlays::~smg_overlays() 509 | { 510 | if (m_pDirect2dFactory != nullptr) 511 | { 512 | m_pDirect2dFactory->Release(); 513 | 514 | m_pDirect2dFactory = nullptr; 515 | } 516 | } 517 | 518 | void smg_overlays::init() 519 | { 520 | HRESULT hr; 521 | if (direct2d_paint) 522 | { 523 | hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, &m_pDirect2dFactory); 524 | if (!SUCCEEDED(hr)) 525 | { 526 | direct2d_paint = false; 527 | } 528 | } 529 | 530 | if (!direct2d_paint) 531 | { 532 | HRESULT hr = BufferedPaintInit(); 533 | g_bDblBuffered = SUCCEEDED(hr); 534 | } 535 | 536 | app_settings->default_init(); 537 | 538 | create_overlay_window_class(); 539 | } 540 | 541 | void smg_overlays::draw_overlay(HWND& hWnd) 542 | { 543 | { 544 | std::shared_lock lock(overlays_list_access); 545 | std::for_each(showing_windows.begin(), showing_windows.end(), [&hWnd](std::shared_ptr& n) { 546 | if (hWnd == n->overlay_hwnd) 547 | { 548 | n->paint_to_window(0); 549 | } 550 | }); 551 | } 552 | } 553 | 554 | -------------------------------------------------------------------------------- /src/sl_overlays_settings.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "sl_overlays_settings.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | void smg_settings::default_init() {} 24 | 25 | smg_settings::smg_settings() : settings_version(0x0002) 26 | { 27 | transparency = 0xD0; 28 | use_color_key = false; 29 | redraw_timeout = 300; 30 | } -------------------------------------------------------------------------------- /src/stdafx.cpp: -------------------------------------------------------------------------------- 1 | // stdafx.cpp : source file that includes just the standard includes 2 | 3 | #include "stdafx.h" 4 | 5 | int try_to_get_dpi(HWND window_handle) 6 | { 7 | int get_dpi = 0; 8 | 9 | static HMODULE user32_dll = 0; 10 | if (!user32_dll) 11 | { 12 | user32_dll = LoadLibrary(L"user32.dll"); 13 | } 14 | 15 | if (user32_dll) 16 | { 17 | typedef UINT(WINAPI * GetDpiForWindow_Fn)(HWND); 18 | GetDpiForWindow_Fn pfnGetDpiForWindow = (GetDpiForWindow_Fn)GetProcAddress(user32_dll, "GetDpiForWindow"); 19 | if (pfnGetDpiForWindow) 20 | { 21 | get_dpi = pfnGetDpiForWindow(window_handle); 22 | } 23 | } 24 | 25 | if(get_dpi == 0) 26 | { 27 | get_dpi = 96; 28 | } 29 | 30 | return get_dpi; 31 | } 32 | 33 | bool set_dpi_awareness() 34 | { 35 | HMODULE user32_dll = LoadLibrary(L"user32.dll"); 36 | if (user32_dll) 37 | { 38 | typedef DPI_AWARENESS_CONTEXT(WINAPI * SetThreadDpiAwarenessContext_Fn)(DPI_AWARENESS_CONTEXT); 39 | SetThreadDpiAwarenessContext_Fn pfnSetDPIAwareness = 40 | (SetThreadDpiAwarenessContext_Fn)GetProcAddress(user32_dll, "SetThreadDpiAwarenessContext"); 41 | if (pfnSetDPIAwareness) 42 | { 43 | pfnSetDPIAwareness(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); 44 | } 45 | FreeLibrary(user32_dll); 46 | } 47 | 48 | return true; 49 | } -------------------------------------------------------------------------------- /src/user_input_callback.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2016-2019 by Streamlabs (General Workings Inc) 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 2 of the License, or 6 | (at your option) any later version. 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | You should have received a copy of the GNU General Public License 12 | along with this program. If not, see . 13 | ******************************************************************************/ 14 | 15 | #include "user_input_callback.h" 16 | #include 17 | #include 18 | #include 19 | #include "overlay_logging.h" 20 | 21 | callback_keyboard_method_t* user_keyboard_callback_info = nullptr; 22 | callback_mouse_method_t* user_mouse_callback_info = nullptr; 23 | 24 | const std::string& translate_to_electron_keycode(const int id) 25 | { 26 | static std::map CodeToKeyCodes = { 27 | {1, "LButton"}, 28 | {2, "RButton"}, 29 | {4, "MButton"}, 30 | {5, "XButotn1"}, 31 | {6, "XButotn2"}, 32 | {8, "Backspace"}, 33 | {9, "Tab"}, 34 | {13, "Enter"}, 35 | {16, "Shift"}, 36 | {17, "Ctrl"}, 37 | {18, "Alt"}, 38 | {19, "Pause"}, 39 | {20, "CapsLock"}, 40 | {27, "Escape"}, 41 | {32, " "}, 42 | {33, "PageUp"}, 43 | {34, "PageDown"}, 44 | {35, "End"}, 45 | {36, "Home"}, 46 | {37, "Left"}, 47 | {38, "Up"}, 48 | {39, "Right"}, 49 | {40, "Down"}, 50 | {45, "Insert"}, 51 | {46, "Delete"}, 52 | {48, "0"}, 53 | {49, "1"}, 54 | {50, "2"}, 55 | {51, "3"}, 56 | {52, "4"}, 57 | {53, "5"}, 58 | {54, "6"}, 59 | {55, "7"}, 60 | {56, "8"}, 61 | {57, "9"}, 62 | {65, "A"}, 63 | {66, "B"}, 64 | {67, "C"}, 65 | {68, "D"}, 66 | {69, "E"}, 67 | {70, "F"}, 68 | {71, "G"}, 69 | {72, "H"}, 70 | {73, "I"}, 71 | {74, "J"}, 72 | {75, "K"}, 73 | {76, "L"}, 74 | {77, "M"}, 75 | {78, "N"}, 76 | {79, "O"}, 77 | {80, "P"}, 78 | {81, "Q"}, 79 | {82, "R"}, 80 | {83, "S"}, 81 | {84, "T"}, 82 | {85, "U"}, 83 | {86, "V"}, 84 | {87, "W"}, 85 | {88, "X"}, 86 | {89, "Y"}, 87 | {90, "Z"}, 88 | {91, "Meta"}, 89 | {92, "Meta"}, 90 | {93, "ContextMenu"}, 91 | {96, "0"}, 92 | {97, "1"}, 93 | {98, "2"}, 94 | {99, "3"}, 95 | {100, "4"}, 96 | {101, "5"}, 97 | {102, "6"}, 98 | {103, "7"}, 99 | {104, "8"}, 100 | {105, "9"}, 101 | {106, "*"}, 102 | {107, "+"}, 103 | {109, "-"}, 104 | {110, "."}, 105 | {111, "/"}, 106 | {112, "F1"}, 107 | {113, "F2"}, 108 | {114, "F3"}, 109 | {115, "F4"}, 110 | {116, "F5"}, 111 | {117, "F6"}, 112 | {118, "F7"}, 113 | {119, "F8"}, 114 | {120, "F9"}, 115 | {121, "F10"}, 116 | {122, "F11"}, 117 | {123, "F12"}, 118 | {144, "NumLock"}, 119 | {145, "ScrollLock"}, 120 | {160, "Shift"}, 121 | {161, "Shift"}, 122 | {162, "Control"}, 123 | {163, "Control"}, 124 | {164, "Alt"}, 125 | {165, "Alt"}, 126 | {182, "My Computer"}, 127 | {183, "My Calculator"}, 128 | {186, ";"}, 129 | {187, "="}, 130 | {188, "},"}, 131 | {189, "-"}, 132 | {190, "."}, 133 | {191, "/"}, 134 | {192, "`"}, 135 | {219, "["}, 136 | {220, "\\"}, 137 | {221, "]"}, 138 | {222, "'"}, 139 | {250, "Play"}, 140 | }; 141 | 142 | return CodeToKeyCodes[id]; 143 | } 144 | 145 | struct wm_event_t 146 | { 147 | WPARAM wParam; 148 | LPARAM lParam; 149 | 150 | wm_event_t(WPARAM _wParam, LPARAM _lParam) noexcept : wParam(_wParam), lParam(_lParam) {} 151 | }; 152 | 153 | callback_method_t::callback_method_t() 154 | { 155 | ready = false; 156 | } 157 | 158 | callback_method_t ::~callback_method_t() 159 | { 160 | log_debug << "APP: ~callback_method_t" << std::endl; 161 | napi_delete_reference(env_this, js_this); 162 | napi_async_destroy(env_this, async_context); 163 | } 164 | 165 | void callback_method_t::callback_method_reset() noexcept 166 | { 167 | initialized = false; 168 | completed = false; 169 | success = false; 170 | error = 0; 171 | result_int = 0; 172 | } 173 | 174 | bool is_intercept_active = false; 175 | bool callback_method_t::set_intercept_active(bool new_state) noexcept 176 | { 177 | is_intercept_active = new_state; 178 | return new_state; 179 | } 180 | 181 | bool callback_method_t::get_intercept_active() noexcept 182 | { 183 | return is_intercept_active; 184 | } 185 | 186 | napi_status callback_keyboard_method_t::set_callback_args_values(napi_env env) 187 | { 188 | log_info << "APP: callback_keyboard_method_t::set_callback_args_values" << std::endl; 189 | napi_status status = napi_ok; 190 | 191 | std::shared_ptr event; 192 | 193 | { 194 | std::lock_guard lock(send_queue_mutex); 195 | event = to_send.front(); 196 | to_send.pop(); 197 | } 198 | 199 | bool send_key = false; 200 | 201 | std::string event_type = "unknown"; 202 | 203 | switch (event->wParam) 204 | { 205 | case WM_KEYDOWN: 206 | event_type = "keyDown"; 207 | send_key = true; 208 | break; 209 | case WM_KEYUP: 210 | event_type = "keyUp"; 211 | send_key = true; 212 | break; 213 | case WM_CHAR: 214 | event_type = "char"; 215 | send_key = true; 216 | break; 217 | default: 218 | break; 219 | }; 220 | 221 | if (status == napi_ok) 222 | { 223 | status = napi_create_string_utf8(env, event_type.c_str(), event_type.size(), &argv_to_cb[0]); 224 | } 225 | 226 | if (send_key) 227 | { 228 | if (status == napi_ok) 229 | { 230 | const LPKBDLLHOOKSTRUCT key = reinterpret_cast(event->lParam); 231 | const std::string& keyCode = translate_to_electron_keycode(key->vkCode); 232 | status = napi_create_string_utf8(env, keyCode.c_str(), keyCode.size(), &argv_to_cb[1]); 233 | } 234 | } 235 | 236 | if (!send_key) 237 | { 238 | status = napi_create_int32(env, 0, &argv_to_cb[1]); 239 | } 240 | 241 | return status; 242 | } 243 | 244 | napi_status callback_mouse_method_t::set_callback_args_values(napi_env env) 245 | { 246 | log_info << "APP: callback_mouse_method_t::set_callback_args_values" << std::endl; 247 | napi_status status = napi_ok; 248 | 249 | std::shared_ptr event; 250 | 251 | { 252 | std::lock_guard lock(send_queue_mutex); 253 | event = to_send.front(); 254 | to_send.pop(); 255 | } 256 | 257 | bool send_mouse = false; 258 | std::string event_type = "unknown"; 259 | std::string mouse_modifiers = ""; 260 | 261 | switch (event->wParam) 262 | { 263 | 264 | case WM_MOUSEMOVE: 265 | event_type = "mouseMove"; 266 | send_mouse = true; 267 | break; 268 | case WM_LBUTTONDOWN: 269 | event_type = "mouseDown"; 270 | mouse_modifiers = "leftButtonDown"; 271 | send_mouse = true; 272 | break; 273 | case WM_LBUTTONUP: 274 | event_type = "mouseUp"; 275 | mouse_modifiers = "leftButtonUp"; 276 | send_mouse = true; 277 | break; 278 | case WM_RBUTTONDOWN: 279 | event_type = "mouseDown"; 280 | mouse_modifiers = "rightButtonDown"; 281 | send_mouse = true; 282 | break; 283 | case WM_RBUTTONUP: 284 | event_type = "mouseUp"; 285 | mouse_modifiers = "rightButtonUp"; 286 | send_mouse = true; 287 | break; 288 | case WM_MOUSEWHEEL: 289 | case WM_MOUSEHWHEEL: 290 | event_type = "mouseWheel"; 291 | mouse_modifiers = ""; 292 | send_mouse = true; 293 | break; 294 | default: 295 | break; 296 | }; 297 | 298 | if (status == napi_ok) 299 | { 300 | status = napi_create_string_utf8(env, event_type.c_str(), event_type.size(), &argv_to_cb[0]); 301 | } 302 | 303 | if (send_mouse) 304 | { 305 | if (status == napi_ok) 306 | { 307 | status = napi_create_int32(env, 100, &argv_to_cb[1]); 308 | } 309 | if (status == napi_ok) 310 | { 311 | status = napi_create_int32(env, 100, &argv_to_cb[2]); 312 | } 313 | if (status == napi_ok) 314 | { 315 | status = napi_create_string_utf8(env, mouse_modifiers.c_str(), mouse_modifiers.size(), &argv_to_cb[3]); 316 | } 317 | } 318 | 319 | if (!send_mouse) 320 | { 321 | status = napi_create_int32(env, 0, &argv_to_cb[1]); 322 | status = napi_create_int32(env, 0, &argv_to_cb[2]); 323 | } 324 | 325 | return status; 326 | } 327 | 328 | int callback_method_t::use_callback(WPARAM wParam, LPARAM lParam) 329 | { 330 | log_info << "APP: use_callback called" << std::endl; 331 | 332 | int ret = -1; 333 | 334 | { 335 | std::lock_guard lock(send_queue_mutex); 336 | to_send.push(std::make_shared(wParam, lParam)); 337 | } 338 | 339 | uv_async_send(&uv_async_this); 340 | 341 | if (to_send.size() > 256) 342 | { 343 | log_info << "APP: Failed to send too many events, will switch input interception off" << std::endl; 344 | ret = switch_input(); 345 | } 346 | 347 | return ret; 348 | } 349 | 350 | int switch_input() 351 | { 352 | log_info << "APP: switch_input " << std::endl; 353 | 354 | int ret = -1; 355 | 356 | callback_method_t::set_intercept_active(!callback_method_t::get_intercept_active()); 357 | 358 | ret = switch_overlays_user_input(callback_method_t::get_intercept_active()); 359 | 360 | return ret; 361 | } 362 | 363 | int use_callback_keyboard(WPARAM wParam, LPARAM lParam) 364 | { 365 | log_info << "APP: use_callback_keyboard " << std::endl; 366 | 367 | int ret = -1; 368 | 369 | callback_method_t* method = user_keyboard_callback_info; 370 | if (method != nullptr) 371 | { 372 | ret = method->use_callback(wParam, lParam); 373 | } 374 | 375 | return ret; 376 | } 377 | 378 | int use_callback_mouse(WPARAM wParam, LPARAM lParam) 379 | { 380 | log_info << "APP: use_callback_mouse " << std::endl; 381 | 382 | int ret = -1; 383 | 384 | callback_method_t* method = user_mouse_callback_info; 385 | if (method != nullptr) 386 | { 387 | ret = method->use_callback(wParam, lParam); 388 | } 389 | 390 | return ret; 391 | } 392 | 393 | void callback_finalize(napi_env env, void* data, void* hint) 394 | { 395 | log_info << "APP: callback_finalize " << std::endl; 396 | } 397 | 398 | napi_status callback_method_t::callback_init(napi_env env, napi_callback_info info, const char* name) 399 | { 400 | size_t argc = 1; 401 | napi_value argv[1]; 402 | napi_value async_name = nullptr; 403 | napi_status status; 404 | 405 | status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr); 406 | 407 | if (status == napi_ok) 408 | { 409 | status = napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &async_name); 410 | } 411 | 412 | if (status == napi_ok) 413 | { 414 | status = napi_async_destroy(env_this, async_context); 415 | 416 | status = napi_async_init(env, argv[0], async_name, &async_context); 417 | uv_async_init(uv_default_loop(), &uv_async_this, &static_async_callback); 418 | uv_async_this.data = this; 419 | env_this = env; 420 | 421 | log_info << "APP: user_input_callback_method_init status = " << status << std::endl; 422 | 423 | if (status == napi_ok) 424 | { 425 | set_callback(); 426 | ready = true; 427 | } 428 | } 429 | 430 | return status; 431 | } 432 | 433 | void callback_method_t::static_async_callback(uv_async_t* handle) 434 | { 435 | try 436 | { 437 | static_cast(handle->data)->async_callback(); 438 | } catch (std::exception&) 439 | { 440 | } catch (...) 441 | {} 442 | } 443 | 444 | void callback_method_t::async_callback() 445 | { 446 | napi_status status; 447 | napi_value js_cb; 448 | napi_value ret_value; 449 | napi_value recv; 450 | 451 | napi_handle_scope scope; 452 | 453 | status = napi_open_handle_scope(env_this, &scope); 454 | if (status == napi_ok) 455 | { 456 | status = napi_get_reference_value(env_this, js_this, &js_cb); 457 | if (status == napi_ok) 458 | { 459 | while (to_send.size() > 0) 460 | { 461 | status = set_callback_args_values(env_this); 462 | if (status == napi_ok) 463 | { 464 | status = napi_create_object(env_this, &recv); 465 | if (status == napi_ok) 466 | { 467 | if (async_context) 468 | { 469 | 470 | status = napi_make_callback( 471 | env_this, async_context, recv, js_cb, get_argc_to_cb(), get_argv_to_cb(), &ret_value); 472 | } else 473 | { 474 | napi_value global; 475 | status = napi_get_global(env_this, &global); 476 | if (status == napi_ok) 477 | { 478 | status = napi_call_function(env_this, global, js_cb, get_argc_to_cb(), get_argv_to_cb(), &ret_value); 479 | } 480 | } 481 | } 482 | } 483 | } 484 | } 485 | 486 | napi_close_handle_scope(env_this, scope); 487 | } 488 | 489 | if (status != napi_ok) 490 | { 491 | log_error << "APP: failed async_callback to send callback with status " << status << std::endl; 492 | while (to_send.size() > 0) 493 | { 494 | to_send.pop(); 495 | } 496 | } 497 | } 498 | 499 | void callback_mouse_method_t::set_callback() 500 | { 501 | set_callback_for_mouse_input(&use_callback_mouse); 502 | } 503 | 504 | void callback_keyboard_method_t::set_callback() 505 | { 506 | set_callback_for_keyboard_input(&use_callback_keyboard); 507 | } 508 | 509 | napi_status napi_create_and_set_named_property(napi_env& env, napi_value& obj, const char* value_name, const int value) noexcept 510 | { 511 | napi_status status; 512 | napi_value set_value; 513 | status = napi_create_int32(env, value, &set_value); 514 | if (status == napi_ok) 515 | { 516 | status = napi_set_named_property(env, obj, value_name, set_value); 517 | } 518 | return status; 519 | } --------------------------------------------------------------------------------