├── cmake ├── .CMakeBuildNumber ├── windows │ ├── defaults.cmake │ ├── buildspec.cmake │ ├── resources │ │ └── resource.rc.in │ ├── compilerconfig.cmake │ └── helpers.cmake ├── macos │ ├── resources │ │ ├── ccache-launcher-c.in │ │ ├── ccache-launcher-cxx.in │ │ ├── distribution.in │ │ └── create-package.cmake.in │ ├── buildspec.cmake │ ├── defaults.cmake │ ├── compilerconfig.cmake │ ├── helpers.cmake │ └── xcode.cmake ├── common │ ├── osconfig.cmake │ ├── ccache.cmake │ ├── buildnumber.cmake │ ├── helpers_common.cmake │ ├── compiler_common.cmake │ ├── bootstrap.cmake │ └── buildspec_common.cmake └── linux │ ├── compilerconfig.cmake │ ├── defaults.cmake │ └── helpers.cmake ├── .gersemirc ├── .github ├── scripts │ ├── utils.zsh │ │ ├── log_output │ │ ├── log_info │ │ ├── log_status │ │ ├── log_warning │ │ ├── log_error │ │ ├── mkcd │ │ ├── log_debug │ │ ├── log_group │ │ ├── check_macos │ │ ├── set_loglevel │ │ ├── setup_ubuntu │ │ └── check_ubuntu │ ├── .Brewfile │ ├── .Aptfile │ ├── utils.pwsh │ │ ├── Ensure-Location.ps1 │ │ ├── Invoke-External.ps1 │ │ └── Logger.ps1 │ ├── Package-Windows.ps1 │ ├── Build-Windows.ps1 │ ├── build-macos │ ├── package-macos │ ├── package-ubuntu │ └── build-ubuntu ├── workflows │ ├── dispatch.yaml │ ├── check-format.yaml │ ├── pr-pull.yaml │ ├── push.yaml │ └── build-project.yaml └── actions │ ├── run-gersemi │ └── action.yaml │ ├── run-clang-format │ └── action.yaml │ ├── check-changes │ └── action.yaml │ ├── build-plugin │ └── action.yaml │ ├── package-plugin │ └── action.yaml │ └── setup-macos-codesigning │ └── action.yaml ├── data ├── red-icon.png ├── green-icon.png ├── red-flash-icon.png ├── red-flash2-icon.png ├── red-green-transition-icon.png └── images.qrc ├── .cmake-format.json ├── .gitignore ├── src ├── plugin-support.h ├── plugin-support.c.in ├── plugin-main.cpp ├── lowerthirdswitcher-widget.hpp └── LowerThirdSwitcher.ui ├── CMakeLists.txt ├── .clang-format ├── README.md ├── CMakePresets.json └── LICENSE /cmake/.CMakeBuildNumber: -------------------------------------------------------------------------------- 1 | 4 -------------------------------------------------------------------------------- /.gersemirc: -------------------------------------------------------------------------------- 1 | warn_about_unknown_commands: false 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_output: -------------------------------------------------------------------------------- 1 | print -PR " ${@}" 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_info: -------------------------------------------------------------------------------- 1 | print -PR "%F{4} =>%f %B${@}%b" 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_status: -------------------------------------------------------------------------------- 1 | print -PR "%F{2} >%f ${@}" 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_warning: -------------------------------------------------------------------------------- 1 | print -PR "::warning::%F{3} => ${@}%f" 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_error: -------------------------------------------------------------------------------- 1 | print -u2 -PR "::error::%F{1} ✖︎%f ${@}" 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/mkcd: -------------------------------------------------------------------------------- 1 | [[ -n ${1} ]] && mkdir -p ${1} && builtin cd ${1} 2 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_debug: -------------------------------------------------------------------------------- 1 | if (( debug )) print -PR -e "::debug::%F{220}DEBUG: ${@}%f" 2 | -------------------------------------------------------------------------------- /data/red-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levi-hrb/obs-plugin-lowerthirdswitcher/HEAD/data/red-icon.png -------------------------------------------------------------------------------- /data/green-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levi-hrb/obs-plugin-lowerthirdswitcher/HEAD/data/green-icon.png -------------------------------------------------------------------------------- /.github/scripts/.Brewfile: -------------------------------------------------------------------------------- 1 | brew "ccache" 2 | brew "coreutils" 3 | brew "cmake" 4 | brew "jq" 5 | brew "xcbeautify" 6 | -------------------------------------------------------------------------------- /data/red-flash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levi-hrb/obs-plugin-lowerthirdswitcher/HEAD/data/red-flash-icon.png -------------------------------------------------------------------------------- /data/red-flash2-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levi-hrb/obs-plugin-lowerthirdswitcher/HEAD/data/red-flash2-icon.png -------------------------------------------------------------------------------- /data/red-green-transition-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levi-hrb/obs-plugin-lowerthirdswitcher/HEAD/data/red-green-transition-icon.png -------------------------------------------------------------------------------- /.github/scripts/.Aptfile: -------------------------------------------------------------------------------- 1 | package 'cmake' 2 | package 'ccache' 3 | package 'git' 4 | package 'jq' 5 | package 'ninja-build', bin: 'ninja' 6 | package 'pkg-config' 7 | -------------------------------------------------------------------------------- /data/images.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | green-icon.png 4 | red-icon.png 5 | red-flash-icon.png 6 | red-flash2-icon.png 7 | red-green-transition-icon.png 8 | 9 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/log_group: -------------------------------------------------------------------------------- 1 | autoload -Uz log_info 2 | 3 | if (( ! ${+_log_group} )) typeset -g _log_group=0 4 | 5 | if (( _log_group )) { 6 | print "::endgroup::" 7 | typeset -g _log_group=0 8 | } 9 | if (( # )) { 10 | print "::group::${@}" 11 | typeset -g _log_group=1 12 | } 13 | -------------------------------------------------------------------------------- /.cmake-format.json: -------------------------------------------------------------------------------- 1 | { 2 | "additional_commands": { 3 | "find_qt": { 4 | "flags": [], 5 | "kwargs": { 6 | "COMPONENTS": "+", 7 | "COMPONENTS_WIN": "+", 8 | "COMPONENTS_MACOS": "+", 9 | "COMPONENTS_LINUX": "+" 10 | } 11 | } 12 | }, 13 | "format": { 14 | "line_width": 100 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/check_macos: -------------------------------------------------------------------------------- 1 | autoload -Uz is-at-least log_group log_info log_error log_status 2 | 3 | log_info 'Checking for Homebrew...' 4 | if (( ! ${+commands[brew]} )) { 5 | log_error 'No Homebrew command found. Please install Homebrew (https://brew.sh)' 6 | return 2 7 | } 8 | 9 | brew bundle --file ${SCRIPT_HOME}/.Brewfile 10 | rehash 11 | log_group 12 | -------------------------------------------------------------------------------- /.github/workflows/dispatch.yaml: -------------------------------------------------------------------------------- 1 | name: Dispatch 2 | run-name: Dispatched Repository Actions - ${{ inputs.job }} ⌛️ 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | job: 7 | description: Dispatch job to run 8 | required: true 9 | type: choice 10 | options: 11 | - build 12 | permissions: 13 | contents: write 14 | jobs: 15 | check-and-build: 16 | if: inputs.job == 'build' 17 | uses: ./.github/workflows/build-project.yaml 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /cmake/windows/defaults.cmake: -------------------------------------------------------------------------------- 1 | # CMake Windows defaults module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # Enable find_package targets to become globally available targets 6 | set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE) 7 | 8 | include(buildspec) 9 | 10 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 11 | set(CMAKE_INSTALL_PREFIX 12 | "$ENV{ALLUSERSPROFILE}/obs-studio/plugins" 13 | CACHE STRING 14 | "Default plugin installation directory" 15 | FORCE 16 | ) 17 | endif() 18 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/set_loglevel: -------------------------------------------------------------------------------- 1 | autoload -Uz log_debug log_error 2 | 3 | local -r _usage="Usage: %B${0}%b 4 | 5 | Set log level, following levels are supported: 0 (quiet), 1 (normal), 2 (verbose), 3 (debug)" 6 | 7 | if (( ! # )); then 8 | log_error 'Called without arguments.' 9 | log_output ${_usage} 10 | return 2 11 | elif (( ${1} >= 4 )); then 12 | log_error 'Called with loglevel > 3.' 13 | log_output ${_usage} 14 | fi 15 | 16 | typeset -g -i -r _loglevel=${1} 17 | log_debug "Log level set to '${1}'" 18 | -------------------------------------------------------------------------------- /.github/workflows/check-format.yaml: -------------------------------------------------------------------------------- 1 | name: Check Code Formatting 🛠️ 2 | on: 3 | workflow_call: 4 | jobs: 5 | clang-format: 6 | runs-on: ubuntu-24.04 7 | steps: 8 | - uses: actions/checkout@v4 9 | with: 10 | fetch-depth: 0 11 | - name: clang-format check 🐉 12 | id: clang-format 13 | uses: ./.github/actions/run-clang-format 14 | with: 15 | failCondition: error 16 | 17 | gersemi: 18 | runs-on: ubuntu-24.04 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | - name: gersemi Check 🎛️ 24 | id: gersemi 25 | uses: ./.github/actions/run-gersemi 26 | with: 27 | failCondition: error 28 | -------------------------------------------------------------------------------- /cmake/macos/resources/ccache-launcher-c.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [[ "$1" == "${CMAKE_C_COMPILER}" ]] ; then 4 | shift 5 | fi 6 | 7 | export CCACHE_DIR='${CMAKE_SOURCE_DIR}/.ccache' 8 | export CCACHE_MAXSIZE='1G' 9 | export CCACHE_CPP2=true 10 | export CCACHE_DEPEND=true 11 | export CCACHE_DIRECT=true 12 | export CCACHE_FILECLONE=true 13 | export CCACHE_INODECACHE=true 14 | export CCACHE_COMPILERCHECK='content' 15 | 16 | CCACHE_SLOPPINESS='file_stat_matches,include_file_mtime,include_file_ctime,system_headers' 17 | 18 | if [[ "${CMAKE_C_COMPILER_ID}" == "AppleClang" ]]; then 19 | CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS},modules,clang_index_store" 20 | fi 21 | export CCACHE_SLOPPINESS 22 | 23 | if [[ "${CI}" ]]; then 24 | export CCACHE_NOHASHDIR=true 25 | fi 26 | exec "${CMAKE_C_COMPILER_LAUNCHER}" "${CMAKE_C_COMPILER}" "$@" 27 | -------------------------------------------------------------------------------- /cmake/macos/resources/ccache-launcher-cxx.in: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [[ "$1" == "${CMAKE_CXX_COMPILER}" ]] ; then 4 | shift 5 | fi 6 | 7 | export CCACHE_DIR='${CMAKE_SOURCE_DIR}/.ccache' 8 | export CCACHE_MAXSIZE='1G' 9 | export CCACHE_CPP2=true 10 | export CCACHE_DEPEND=true 11 | export CCACHE_DIRECT=true 12 | export CCACHE_FILECLONE=true 13 | export CCACHE_INODECACHE=true 14 | export CCACHE_COMPILERCHECK='content' 15 | 16 | CCACHE_SLOPPINESS='file_stat_matches,include_file_mtime,include_file_ctime,system_headers' 17 | 18 | if [[ "${CMAKE_C_COMPILER_ID}" == "AppleClang" ]]; then 19 | CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS},modules,clang_index_store" 20 | fi 21 | export CCACHE_SLOPPINESS 22 | 23 | if [[ "${CI}" ]]; then 24 | export CCACHE_NOHASHDIR=true 25 | fi 26 | exec "${CMAKE_CXX_COMPILER_LAUNCHER}" "${CMAKE_CXX_COMPILER}" "$@" 27 | -------------------------------------------------------------------------------- /.github/workflows/pr-pull.yaml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | run-name: ${{ github.event.pull_request.title }} pull request run 🚀 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths-ignore: 7 | - '**.md' 8 | branches: [master, main] 9 | types: [ opened, synchronize, reopened ] 10 | permissions: 11 | contents: read 12 | concurrency: 13 | group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' 14 | cancel-in-progress: true 15 | jobs: 16 | check-format: 17 | name: Check Formatting 🔍 18 | uses: ./.github/workflows/check-format.yaml 19 | permissions: 20 | contents: read 21 | 22 | build-project: 23 | name: Build Project 🧱 24 | uses: ./.github/workflows/build-project.yaml 25 | secrets: inherit 26 | permissions: 27 | contents: read 28 | -------------------------------------------------------------------------------- /.github/scripts/utils.pwsh/Ensure-Location.ps1: -------------------------------------------------------------------------------- 1 | function Ensure-Location { 2 | <# 3 | .SYNOPSIS 4 | Ensures current location to be set to specified directory. 5 | .DESCRIPTION 6 | If specified directory exists, switch to it. Otherwise create it, 7 | then switch. 8 | .EXAMPLE 9 | Ensure-Location "My-Directory" 10 | Ensure-Location -Path "Path-To-My-Directory" 11 | #> 12 | 13 | param( 14 | [Parameter(Mandatory)] 15 | [string] $Path 16 | ) 17 | 18 | if ( ! ( Test-Path $Path ) ) { 19 | $_Params = @{ 20 | ItemType = "Directory" 21 | Path = ${Path} 22 | ErrorAction = "SilentlyContinue" 23 | } 24 | 25 | New-Item @_Params | Set-Location 26 | } else { 27 | Set-Location -Path ${Path} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cmake/common/osconfig.cmake: -------------------------------------------------------------------------------- 1 | # CMake operating system bootstrap module 2 | 3 | include_guard(GLOBAL) 4 | 5 | if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") 6 | set(CMAKE_C_EXTENSIONS FALSE) 7 | set(CMAKE_CXX_EXTENSIONS FALSE) 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows") 9 | set(OS_WINDOWS TRUE) 10 | elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") 11 | set(CMAKE_C_EXTENSIONS FALSE) 12 | set(CMAKE_CXX_EXTENSIONS FALSE) 13 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos") 14 | set(OS_MACOS TRUE) 15 | elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD") 16 | set(CMAKE_CXX_EXTENSIONS FALSE) 17 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/linux") 18 | string(TOUPPER "${CMAKE_HOST_SYSTEM_NAME}" _SYSTEM_NAME_U) 19 | set(OS_${_SYSTEM_NAME_U} TRUE) 20 | endif() 21 | -------------------------------------------------------------------------------- /cmake/windows/buildspec.cmake: -------------------------------------------------------------------------------- 1 | # CMake Windows build dependencies module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(buildspec_common) 6 | 7 | # _check_dependencies_windows: Set up Windows slice for _check_dependencies 8 | function(_check_dependencies_windows) 9 | set(arch ${CMAKE_VS_PLATFORM_NAME}) 10 | set(platform windows-${arch}) 11 | 12 | set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps") 13 | set(prebuilt_filename "windows-deps-VERSION-ARCH-REVISION.zip") 14 | set(prebuilt_destination "obs-deps-VERSION-ARCH") 15 | set(qt6_filename "windows-deps-qt6-VERSION-ARCH-REVISION.zip") 16 | set(qt6_destination "obs-deps-qt6-VERSION-ARCH") 17 | set(obs-studio_filename "VERSION.zip") 18 | set(obs-studio_destination "obs-studio-VERSION") 19 | set(dependencies_list prebuilt qt6 obs-studio) 20 | 21 | _check_dependencies() 22 | endfunction() 23 | 24 | _check_dependencies_windows() 25 | -------------------------------------------------------------------------------- /cmake/common/ccache.cmake: -------------------------------------------------------------------------------- 1 | # OBS CMake ccache module 2 | 3 | include_guard(GLOBAL) 4 | 5 | if(NOT DEFINED CCACHE_PROGRAM) 6 | message(DEBUG "Trying to find ccache on build host") 7 | find_program(CCACHE_PROGRAM "ccache") 8 | mark_as_advanced(CCACHE_PROGRAM) 9 | endif() 10 | 11 | if(CCACHE_PROGRAM) 12 | message(DEBUG "Trying to find ccache on build host - done") 13 | message(DEBUG "Ccache found as ${CCACHE_PROGRAM}") 14 | option(ENABLE_CCACHE "Enable compiler acceleration with ccache" OFF) 15 | 16 | if(ENABLE_CCACHE) 17 | set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") 18 | set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") 19 | set(CMAKE_OBJC_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") 20 | set(CMAKE_OBJCXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") 21 | set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") 22 | endif() 23 | else() 24 | message(DEBUG "Trying to find ccache on build host - skipped") 25 | endif() 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | ._* 6 | 7 | # Build directories 8 | /build/ 9 | /build_*/ 10 | /release/ 11 | /installer/Output/ 12 | /.deps/ 13 | 14 | # IDEs 15 | .vscode/ 16 | .idea/ 17 | *.swp 18 | *.swo 19 | *~ 20 | *.code-workspace 21 | 22 | # CMake 23 | CMakeCache.txt 24 | CMakeFiles/ 25 | cmake_install.cmake 26 | install_manifest.txt 27 | CTestTestfile.cmake 28 | _deps/ 29 | 30 | # Generated files 31 | *.generated.* 32 | **/.Brewfile.lock.json 33 | 34 | # Backup files 35 | *.old 36 | *.bak 37 | *.backup 38 | 39 | # Compiled Object files 40 | *.o 41 | *.obj 42 | *.lo 43 | *.slo 44 | 45 | # Compiled Dynamic libraries 46 | *.so 47 | *.dylib 48 | *.dll 49 | 50 | # Compiled Static libraries 51 | *.a 52 | *.lib 53 | 54 | # Debug files 55 | *.dSYM/ 56 | *.su 57 | *.idb 58 | *.pdb 59 | 60 | # Qt 61 | *.autosave 62 | *_autogen/ 63 | moc_*.cpp 64 | qrc_*.cpp 65 | ui_*.h 66 | 67 | # Logs 68 | *.log 69 | 70 | # Temporary files 71 | *.tmp 72 | *.temp 73 | -------------------------------------------------------------------------------- /cmake/common/buildnumber.cmake: -------------------------------------------------------------------------------- 1 | # CMake build number module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # Define build number cache file 6 | set(_BUILD_NUMBER_CACHE 7 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/.CMakeBuildNumber" 8 | CACHE INTERNAL 9 | "OBS build number cache file" 10 | ) 11 | 12 | # Read build number from cache file or manual override 13 | if(NOT DEFINED PLUGIN_BUILD_NUMBER) 14 | if(EXISTS "${_BUILD_NUMBER_CACHE}") 15 | file(READ "${_BUILD_NUMBER_CACHE}" PLUGIN_BUILD_NUMBER) 16 | math(EXPR PLUGIN_BUILD_NUMBER "${PLUGIN_BUILD_NUMBER}+1") 17 | else() 18 | if("$ENV{CI}") 19 | if("$ENV{GITHUB_RUN_ID}") 20 | set(PLUGIN_BUILD_NUMBER "$ENV{GITHUB_RUN_ID}") 21 | elseif("$ENV{GITLAB_RUN_ID}") 22 | set(PLUGIN_BUILD_NUMBER "$ENV{GITLAB_RUN_ID}") 23 | else() 24 | set(PLUGIN_BUILD_NUMBER "1") 25 | endif() 26 | endif() 27 | endif() 28 | file(WRITE "${_BUILD_NUMBER_CACHE}" "${PLUGIN_BUILD_NUMBER}") 29 | endif() 30 | -------------------------------------------------------------------------------- /cmake/windows/resources/resource.rc.in: -------------------------------------------------------------------------------- 1 | 1 VERSIONINFO 2 | FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0 3 | PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0 4 | FILEFLAGSMASK 0x0L 5 | #ifdef _DEBUG 6 | FILEFLAGS 0x1L 7 | #else 8 | FILEFLAGS 0x0L 9 | #endif 10 | FILEOS 0x0L 11 | FILETYPE 0x2L 12 | FILESUBTYPE 0x0L 13 | BEGIN 14 | BLOCK "StringFileInfo" 15 | BEGIN 16 | BLOCK "040904b0" 17 | BEGIN 18 | VALUE "CompanyName", "${PLUGIN_AUTHOR}" 19 | VALUE "FileDescription", "${PROJECT_NAME}" 20 | VALUE "FileVersion", "${PROJECT_VERSION}" 21 | VALUE "InternalName", "${PROJECT_NAME}" 22 | VALUE "LegalCopyright", "(C) ${CURRENT_YEAR} ${PLUGIN_AUTHOR}" 23 | VALUE "OriginalFilename", "${PROJECT_NAME}" 24 | VALUE "ProductName", "${PROJECT_NAME}" 25 | VALUE "ProductVersion", "${PROJECT_VERSION}" 26 | END 27 | END 28 | BLOCK "VarFileInfo" 29 | BEGIN 30 | VALUE "Translation", 0x409, 1200 31 | END 32 | END 33 | -------------------------------------------------------------------------------- /.github/scripts/utils.pwsh/Invoke-External.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-External { 2 | <# 3 | .SYNOPSIS 4 | Invokes a non-PowerShell command. 5 | .DESCRIPTION 6 | Runs a non-PowerShell command, and captures its return code. 7 | Throws an exception if the command returns non-zero. 8 | .EXAMPLE 9 | Invoke-External 7z x $MyArchive 10 | #> 11 | 12 | if ( $args.Count -eq 0 ) { 13 | throw 'Invoke-External called without arguments.' 14 | } 15 | 16 | if ( ! ( Test-Path function:Log-Information ) ) { 17 | . $PSScriptRoot/Logger.ps1 18 | } 19 | 20 | $Command = $args[0] 21 | $CommandArgs = @() 22 | 23 | if ( $args.Count -gt 1) { 24 | $CommandArgs = $args[1..($args.Count - 1)] 25 | } 26 | 27 | $_EAP = $ErrorActionPreference 28 | $ErrorActionPreference = "Continue" 29 | 30 | Log-Debug "Invoke-External: ${Command} ${CommandArgs}" 31 | 32 | & $command $commandArgs 33 | $Result = $LASTEXITCODE 34 | 35 | $ErrorActionPreference = $_EAP 36 | 37 | if ( $Result -ne 0 ) { 38 | throw "${Command} ${CommandArgs} exited with non-zero code ${Result}." 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/plugin-support.h: -------------------------------------------------------------------------------- 1 | /* 2 | Plugin Name 3 | Copyright (C) 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program. If not, see 17 | */ 18 | 19 | #pragma once 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | extern const char *PLUGIN_NAME; 31 | extern const char *PLUGIN_VERSION; 32 | 33 | void obs_log(int log_level, const char *format, ...); 34 | extern void blogva(int log_level, const char *format, va_list args); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | -------------------------------------------------------------------------------- /cmake/macos/buildspec.cmake: -------------------------------------------------------------------------------- 1 | # CMake macOS build dependencies module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(buildspec_common) 6 | 7 | # _check_dependencies_macos: Set up macOS slice for _check_dependencies 8 | function(_check_dependencies_macos) 9 | set(arch universal) 10 | set(platform macos) 11 | 12 | file(READ "${CMAKE_CURRENT_SOURCE_DIR}/buildspec.json" buildspec) 13 | 14 | set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps") 15 | set(prebuilt_filename "macos-deps-VERSION-ARCH_REVISION.tar.xz") 16 | set(prebuilt_destination "obs-deps-VERSION-ARCH") 17 | set(qt6_filename "macos-deps-qt6-VERSION-ARCH-REVISION.tar.xz") 18 | set(qt6_destination "obs-deps-qt6-VERSION-ARCH") 19 | set(obs-studio_filename "VERSION.tar.gz") 20 | set(obs-studio_destination "obs-studio-VERSION") 21 | set(dependencies_list prebuilt qt6 obs-studio) 22 | 23 | _check_dependencies() 24 | 25 | execute_process( 26 | COMMAND "xattr" -r -d com.apple.quarantine "${dependencies_dir}" 27 | RESULT_VARIABLE result 28 | COMMAND_ERROR_IS_FATAL ANY 29 | ) 30 | 31 | list(APPEND CMAKE_FRAMEWORK_PATH "${dependencies_dir}/Frameworks") 32 | set(CMAKE_FRAMEWORK_PATH ${CMAKE_FRAMEWORK_PATH} PARENT_SCOPE) 33 | endfunction() 34 | 35 | _check_dependencies_macos() 36 | -------------------------------------------------------------------------------- /src/plugin-support.c.in: -------------------------------------------------------------------------------- 1 | /* 2 | Plugin Name 3 | Copyright (C) 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program. If not, see 17 | */ 18 | 19 | #include 20 | 21 | const char *PLUGIN_NAME = "@CMAKE_PROJECT_NAME@"; 22 | const char *PLUGIN_VERSION = "@CMAKE_PROJECT_VERSION@"; 23 | 24 | void obs_log(int log_level, const char *format, ...) 25 | { 26 | size_t length = 4 + strlen(PLUGIN_NAME) + strlen(format); 27 | 28 | char *template = malloc(length + 1); 29 | 30 | snprintf(template, length, "[%s] %s", PLUGIN_NAME, format); 31 | 32 | va_list(args); 33 | 34 | va_start(args, format); 35 | blogva(log_level, template, args); 36 | va_end(args); 37 | 38 | free(template); 39 | } 40 | -------------------------------------------------------------------------------- /cmake/macos/defaults.cmake: -------------------------------------------------------------------------------- 1 | # CMake macOS defaults module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # Set empty codesigning team if not specified as cache variable 6 | if(NOT CODESIGN_TEAM) 7 | set(CODESIGN_TEAM "" CACHE STRING "OBS code signing team for macOS" FORCE) 8 | 9 | # Set ad-hoc codesigning identity if not specified as cache variable 10 | if(NOT CODESIGN_IDENTITY) 11 | set(CODESIGN_IDENTITY 12 | "-" 13 | CACHE STRING 14 | "OBS code signing identity for macOS" 15 | FORCE 16 | ) 17 | endif() 18 | endif() 19 | 20 | include(xcode) 21 | 22 | include(buildspec) 23 | 24 | # Use Applications directory as default install destination 25 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 26 | set(CMAKE_INSTALL_PREFIX 27 | "$ENV{HOME}/Library/Application Support/obs-studio/plugins" 28 | CACHE STRING 29 | "Default plugin installation directory" 30 | FORCE 31 | ) 32 | endif() 33 | 34 | # Enable find_package targets to become globally available targets 35 | set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE) 36 | # Enable RPATH support for generated binaries 37 | set(CMAKE_MACOSX_RPATH TRUE) 38 | # Use RPATHs from build tree _in_ the build tree 39 | set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) 40 | # Do not add default linker search paths to RPATH 41 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE) 42 | # Use common bundle-relative RPATH for installed targets 43 | set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks") 44 | -------------------------------------------------------------------------------- /cmake/macos/resources/distribution.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | @CMAKE_PROJECT_NAME@ 10 | 11 | 12 | 13 | 14 | 15 | 16 | #@CMAKE_PROJECT_NAME@.pkg 17 | 18 | 33 | 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28...3.30) 2 | 3 | include( 4 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake" 5 | NO_POLICY_SCOPE 6 | ) 7 | 8 | project(${_name} VERSION ${_version}) 9 | 10 | option(ENABLE_FRONTEND_API "Use obs-frontend-api for UI functionality" ON) 11 | option(ENABLE_QT "Use Qt functionality" ON) 12 | 13 | include(compilerconfig) 14 | include(defaults) 15 | include(helpers) 16 | 17 | add_library(${CMAKE_PROJECT_NAME} MODULE) 18 | 19 | find_package(libobs REQUIRED) 20 | target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE OBS::libobs) 21 | 22 | if(ENABLE_FRONTEND_API) 23 | find_package(obs-frontend-api REQUIRED) 24 | target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE OBS::obs-frontend-api) 25 | endif() 26 | 27 | if(ENABLE_QT) 28 | find_package(Qt6 COMPONENTS Widgets Core) 29 | target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets) 30 | target_compile_options( 31 | ${CMAKE_PROJECT_NAME} 32 | PRIVATE 33 | $<$:-Wno-quoted-include-in-framework-header 34 | -Wno-comma> 35 | ) 36 | set_target_properties( 37 | ${CMAKE_PROJECT_NAME} 38 | PROPERTIES AUTOMOC ON AUTOUIC ON AUTORCC ON 39 | ) 40 | endif() 41 | 42 | # Add plugin source files 43 | target_sources( 44 | ${CMAKE_PROJECT_NAME} 45 | PRIVATE 46 | src/plugin-main.cpp 47 | src/lowerthirdswitcher-widget.cpp 48 | src/lowerthirdswitcher-widget.hpp 49 | src/LowerThirdSwitcher.ui 50 | data/images.qrc 51 | ) 52 | 53 | set_target_properties_plugin( 54 | ${CMAKE_PROJECT_NAME} 55 | PROPERTIES OUTPUT_NAME ${_name} 56 | ) 57 | -------------------------------------------------------------------------------- /src/plugin-main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Lower Third Switcher Plugin 3 | Copyright (C) 2025 Levi Herber 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program. If not, see 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include "lowerthirdswitcher-widget.hpp" 23 | 24 | OBS_DECLARE_MODULE() 25 | OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") 26 | 27 | LowerthirdswitcherDockWidget *lowerthirdswitcherDockWidget = nullptr; 28 | 29 | bool obs_module_load(void) 30 | { 31 | const auto main_window = 32 | static_cast(obs_frontend_get_main_window()); 33 | lowerthirdswitcherDockWidget = 34 | new LowerthirdswitcherDockWidget(main_window); 35 | 36 | obs_frontend_add_dock_by_id("LowerThirdSwitcherDock", 37 | "Lower Third Switcher", 38 | lowerthirdswitcherDockWidget); 39 | 40 | obs_log(LOG_INFO, 41 | "LowerThirdSwitcher plugin loaded successfully (version %s)", 42 | PLUGIN_VERSION); 43 | return true; 44 | } 45 | 46 | void obs_module_unload() 47 | { 48 | obs_log(LOG_INFO, "LowerThirdSwitcher plugin unloaded"); 49 | } 50 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/setup_ubuntu: -------------------------------------------------------------------------------- 1 | autoload -Uz log_error log_status log_info mkcd 2 | 3 | if (( ! ${+project_root} )) { 4 | log_error "'project_root' not set. Please set before running ${0}." 5 | return 2 6 | } 7 | 8 | if (( ! ${+target} )) { 9 | log_error "'target' not set. Please set before running ${0}." 10 | return 2 11 | } 12 | 13 | pushd ${project_root} 14 | 15 | typeset -g QT_VERSION 16 | 17 | local -a apt_args=( 18 | ${CI:+-y} 19 | --no-install-recommends 20 | ) 21 | if (( _loglevel == 0 )) apt_args+=(--quiet) 22 | 23 | if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) { 24 | log_group 'Installing obs-studio build dependencies...' 25 | 26 | local suffix 27 | if [[ ${CPUTYPE} != "${target##*-}" ]] { 28 | local -A arch_mappings=( 29 | aarch64 arm64 30 | x86_64 amd64 31 | ) 32 | 33 | suffix=":${arch_mappings[${target##*-}]}" 34 | 35 | sudo apt-get install ${apt_args} gcc-${${target##*-}//_/-}-linux-gnu g++-${${target##*-}//_/-}-linux-gnu 36 | } 37 | 38 | sudo add-apt-repository --yes ppa:obsproject/obs-studio 39 | sudo apt update 40 | 41 | sudo apt-get install ${apt_args} \ 42 | build-essential \ 43 | libgles2-mesa-dev \ 44 | libsimde-dev \ 45 | obs-studio 46 | 47 | local -a _qt_packages=() 48 | 49 | if (( QT_VERSION == 5 )) { 50 | _qt_packages+=( 51 | qtbase5-dev${suffix} 52 | libqt5svg5-dev${suffix} 53 | qtbase5-private-dev${suffix} 54 | libqt5x11extras5-dev${suffix} 55 | ) 56 | } else { 57 | _qt_packages+=( 58 | qt6-base-dev${suffix} 59 | libqt6svg6-dev${suffix} 60 | qt6-base-private-dev${suffix} 61 | ) 62 | } 63 | 64 | sudo apt-get install ${apt_args} ${_qt_packages} 65 | log_group 66 | } 67 | -------------------------------------------------------------------------------- /.github/scripts/utils.zsh/check_ubuntu: -------------------------------------------------------------------------------- 1 | autoload -Uz log_info log_status log_error log_debug log_warning log_group 2 | 3 | log_group 'Check Linux build requirements' 4 | log_debug 'Checking Linux distribution name and version...' 5 | 6 | # Check for Ubuntu version 22.10 or later, which have srt and librist available via apt-get 7 | typeset -g -i UBUNTU_2210_OR_LATER=0 8 | if [[ -f /etc/os_release ]] { 9 | local dist_name 10 | local dist_version 11 | read -r dist_name dist_version <<< "$(source /etc/os_release; print "${NAME} ${VERSION_ID}")" 12 | 13 | autoload -Uz is-at-least 14 | if [[ ${dist_name} == Ubuntu ]] && is-at-least 22.10 ${dist_version}; then 15 | typeset -g -i UBUNTU_2210_OR_LATER=1 16 | fi 17 | } 18 | 19 | log_debug 'Checking for apt-get...' 20 | if (( ! ${+commands[apt-get]} )) { 21 | log_error 'No apt-get command found. Please install apt' 22 | return 2 23 | } else { 24 | log_debug "Apt-get located at ${commands[apt-get]}" 25 | } 26 | 27 | local -a dependencies=("${(fA)$(<${SCRIPT_HOME}/.Aptfile)}") 28 | local -a install_list 29 | local binary 30 | 31 | sudo apt-get update -qq 32 | 33 | for dependency (${dependencies}) { 34 | local -a tokens=(${=dependency//(,|:|\')/}) 35 | 36 | if [[ ! ${tokens[1]} == 'package' ]] continue 37 | 38 | if [[ ${#tokens} -gt 2 && ${tokens[3]} == 'bin' ]] { 39 | binary=${tokens[4]} 40 | } else { 41 | binary=${tokens[2]} 42 | } 43 | 44 | if (( ! ${+commands[${binary}]} )) install_list+=(${tokens[2]}) 45 | } 46 | 47 | log_debug "List of dependencies to install: ${install_list}" 48 | if (( ${#install_list} )) { 49 | if (( ! ${+CI} )) log_warning 'Dependency installation via apt may require elevated privileges' 50 | 51 | local -a apt_args=( 52 | ${CI:+-y} 53 | --no-install-recommends 54 | ) 55 | if (( _loglevel == 0 )) apt_args+=(--quiet) 56 | 57 | sudo apt-get ${apt_args} install ${install_list} 58 | } 59 | 60 | rehash 61 | log_group 62 | -------------------------------------------------------------------------------- /cmake/macos/resources/create-package.cmake.in: -------------------------------------------------------------------------------- 1 | make_directory("$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package/Library/Application Support/obs-studio/plugins") 2 | 3 | if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin" AND NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin") 4 | file(INSTALL DESTINATION "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package/Library/Application Support/obs-studio/plugins" 5 | TYPE DIRECTORY FILES "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin" USE_SOURCE_PERMISSIONS) 6 | 7 | if(CMAKE_INSTALL_CONFIG_NAME MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$" OR CMAKE_INSTALL_CONFIG_NAME MATCHES "^([Mm][Ii][Nn][Ss][Ii][Zz][Ee][Rr][Ee][Ll])$") 8 | if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin.dSYM" AND NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin.dSYM") 9 | file(INSTALL DESTINATION "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package/Library/Application Support/obs-studio/plugins" TYPE DIRECTORY FILES "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin.dSYM" USE_SOURCE_PERMISSIONS) 10 | endif() 11 | endif() 12 | endif() 13 | 14 | make_directory("$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp") 15 | 16 | execute_process( 17 | COMMAND /usr/bin/pkgbuild 18 | --identifier '@MACOS_BUNDLEID@' 19 | --version '@CMAKE_PROJECT_VERSION@' 20 | --root "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package" 21 | "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp/@CMAKE_PROJECT_NAME@.pkg" 22 | COMMAND_ERROR_IS_FATAL ANY 23 | ) 24 | 25 | execute_process( 26 | COMMAND /usr/bin/productbuild 27 | --distribution "@CMAKE_CURRENT_BINARY_DIR@/distribution" 28 | --package-path "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp" 29 | "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.pkg" 30 | COMMAND_ERROR_IS_FATAL ANY) 31 | 32 | if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.pkg") 33 | file(REMOVE_RECURSE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp") 34 | file(REMOVE_RECURSE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package") 35 | endif() 36 | -------------------------------------------------------------------------------- /cmake/windows/compilerconfig.cmake: -------------------------------------------------------------------------------- 1 | # CMake Windows compiler configuration module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(compiler_common) 6 | 7 | set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase) 8 | 9 | message( 10 | DEBUG 11 | "Current Windows API version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}" 12 | ) 13 | if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM) 14 | message( 15 | DEBUG 16 | "Maximum Windows API version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM}" 17 | ) 18 | endif() 19 | 20 | if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS 10.0.20348) 21 | message( 22 | FATAL_ERROR 23 | "OBS requires Windows 10 SDK version 10.0.20348.0 or more recent.\n" 24 | "Please download and install the most recent Windows platform SDK." 25 | ) 26 | endif() 27 | 28 | set(_obs_msvc_c_options /MP /Zc:__cplusplus /Zc:preprocessor) 29 | set(_obs_msvc_cpp_options /MP /Zc:__cplusplus /Zc:preprocessor) 30 | 31 | if(CMAKE_CXX_STANDARD GREATER_EQUAL 20) 32 | list(APPEND _obs_msvc_cpp_options /Zc:char8_t-) 33 | endif() 34 | 35 | add_compile_options( 36 | /W3 37 | /utf-8 38 | /Brepro 39 | /permissive- 40 | "$<$:${_obs_msvc_c_options}>" 41 | "$<$:${_obs_msvc_cpp_options}>" 42 | "$<$:${_obs_clang_c_options}>" 43 | "$<$:${_obs_clang_cxx_options}>" 44 | $<$>:/Gy> 45 | $<$>:/GL> 46 | $<$>:/Oi> 47 | ) 48 | 49 | add_compile_definitions( 50 | UNICODE 51 | _UNICODE 52 | _CRT_SECURE_NO_WARNINGS 53 | _CRT_NONSTDC_NO_WARNINGS 54 | $<$:DEBUG> 55 | $<$:_DEBUG> 56 | ) 57 | 58 | add_link_options( 59 | $<$>:/OPT:REF> 60 | $<$>:/OPT:ICF> 61 | $<$>:/LTCG> 62 | $<$>:/INCREMENTAL:NO> 63 | /DEBUG 64 | /Brepro 65 | ) 66 | 67 | if(CMAKE_COMPILE_WARNING_AS_ERROR) 68 | add_link_options(/WX) 69 | endif() 70 | -------------------------------------------------------------------------------- /cmake/common/helpers_common.cmake: -------------------------------------------------------------------------------- 1 | # CMake common helper functions module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # check_uuid: Helper function to check for valid UUID 6 | function(check_uuid uuid_string return_value) 7 | set(valid_uuid TRUE) 8 | # gersemi: off 9 | set(uuid_token_lengths 8 4 4 4 12) 10 | # gersemi: on 11 | set(token_num 0) 12 | 13 | string(REPLACE "-" ";" uuid_tokens ${uuid_string}) 14 | list(LENGTH uuid_tokens uuid_num_tokens) 15 | 16 | if(uuid_num_tokens EQUAL 5) 17 | message(DEBUG "UUID ${uuid_string} is valid with 5 tokens.") 18 | foreach(uuid_token IN LISTS uuid_tokens) 19 | list(GET uuid_token_lengths ${token_num} uuid_target_length) 20 | string(LENGTH "${uuid_token}" uuid_actual_length) 21 | if(uuid_actual_length EQUAL uuid_target_length) 22 | string(REGEX MATCH "[0-9a-fA-F]+" uuid_hex_match ${uuid_token}) 23 | if(NOT uuid_hex_match STREQUAL uuid_token) 24 | set(valid_uuid FALSE) 25 | break() 26 | endif() 27 | else() 28 | set(valid_uuid FALSE) 29 | break() 30 | endif() 31 | math(EXPR token_num "${token_num}+1") 32 | endforeach() 33 | else() 34 | set(valid_uuid FALSE) 35 | endif() 36 | message(DEBUG "UUID ${uuid_string} valid: ${valid_uuid}") 37 | set(${return_value} ${valid_uuid} PARENT_SCOPE) 38 | endfunction() 39 | 40 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/plugin-support.c.in") 41 | configure_file(src/plugin-support.c.in plugin-support.c @ONLY) 42 | add_library(plugin-support STATIC) 43 | target_sources( 44 | plugin-support 45 | PRIVATE plugin-support.c 46 | PUBLIC src/plugin-support.h 47 | ) 48 | target_include_directories( 49 | plugin-support 50 | PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src" 51 | ) 52 | if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD) 53 | # add fPIC on Linux to prevent shared object errors 54 | set_property( 55 | TARGET plugin-support 56 | PROPERTY POSITION_INDEPENDENT_CODE ON 57 | ) 58 | endif() 59 | endif() 60 | -------------------------------------------------------------------------------- /.github/actions/run-gersemi/action.yaml: -------------------------------------------------------------------------------- 1 | name: Run gersemi 2 | description: Runs gersemi and checks for any changes introduced by it 3 | inputs: 4 | failCondition: 5 | description: Controls whether failed checks also fail the workflow run 6 | required: false 7 | default: never 8 | workingDirectory: 9 | description: Working directory for checks 10 | required: false 11 | default: ${{ github.workspace }} 12 | runs: 13 | using: composite 14 | steps: 15 | - name: Check Runner Operating System 🏃‍♂️ 16 | if: runner.os == 'Windows' 17 | shell: bash 18 | run: | 19 | : Check Runner Operating System 🏃‍♂️ 20 | echo "::notice::run-gersemi action requires a macOS-based or Linux-based runner." 21 | exit 2 22 | 23 | - name: Check for Changed Files ✅ 24 | uses: ./.github/actions/check-changes 25 | id: checks 26 | with: 27 | checkGlob: "'*.cmake' '*CMakeLists.txt'" 28 | diffFilter: 'ACM' 29 | 30 | - name: Install Dependencies 🛍️ 31 | if: runner.os == 'Linux' && fromJSON(steps.checks.outputs.hasChangedFiles) 32 | shell: bash 33 | run: | 34 | : Install Dependencies 🛍️ 35 | echo ::group::Install Dependencies 36 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 37 | echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH 38 | brew install --quiet zsh 39 | echo ::endgroup:: 40 | 41 | - name: Run gersemi 🎛️ 42 | if: fromJSON(steps.checks.outputs.hasChangedFiles) 43 | id: result 44 | shell: zsh --no-rcs --errexit --pipefail {0} 45 | working-directory: ${{ github.workspace }} 46 | env: 47 | CHANGED_FILES: ${{ steps.checks.outputs.changedFiles }} 48 | run: | 49 | : Run gersemi 🎛️ 50 | if (( ${+RUNNER_DEBUG} )) setopt XTRACE 51 | 52 | print ::group::Install gersemi 53 | brew install --quiet obsproject/tools/gersemi 54 | print ::endgroup:: 55 | 56 | print ::group::Run gersemi 57 | local -a changes=(${(s:,:)CHANGED_FILES//[\[\]\'\"]/}) 58 | ./build-aux/run-gersemi --fail-${{ inputs.failCondition }} --check ${changes} 59 | print ::endgroup:: 60 | -------------------------------------------------------------------------------- /.github/scripts/Package-Windows.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [ValidateSet('x64')] 4 | [string] $Target = 'x64', 5 | [ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')] 6 | [string] $Configuration = 'RelWithDebInfo' 7 | ) 8 | 9 | $ErrorActionPreference = 'Stop' 10 | 11 | if ( $DebugPreference -eq 'Continue' ) { 12 | $VerbosePreference = 'Continue' 13 | $InformationPreference = 'Continue' 14 | } 15 | 16 | if ( $env:CI -eq $null ) { 17 | throw "Package-Windows.ps1 requires CI environment" 18 | } 19 | 20 | if ( ! ( [System.Environment]::Is64BitOperatingSystem ) ) { 21 | throw "Packaging script requires a 64-bit system to build and run." 22 | } 23 | 24 | if ( $PSVersionTable.PSVersion -lt '7.2.0' ) { 25 | Write-Warning 'The packaging script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6' 26 | exit 2 27 | } 28 | 29 | function Package { 30 | trap { 31 | Write-Error $_ 32 | exit 2 33 | } 34 | 35 | $ScriptHome = $PSScriptRoot 36 | $ProjectRoot = Resolve-Path -Path "$PSScriptRoot/../.." 37 | $BuildSpecFile = "${ProjectRoot}/buildspec.json" 38 | 39 | $UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse 40 | 41 | foreach( $Utility in $UtilityFunctions ) { 42 | Write-Debug "Loading $($Utility.FullName)" 43 | . $Utility.FullName 44 | } 45 | 46 | $BuildSpec = Get-Content -Path ${BuildSpecFile} -Raw | ConvertFrom-Json 47 | $ProductName = $BuildSpec.name 48 | $ProductVersion = $BuildSpec.version 49 | 50 | $OutputName = "${ProductName}-${ProductVersion}-windows-${Target}" 51 | 52 | $RemoveArgs = @{ 53 | ErrorAction = 'SilentlyContinue' 54 | Path = @( 55 | "${ProjectRoot}/release/${ProductName}-*-windows-*.zip" 56 | ) 57 | } 58 | 59 | Remove-Item @RemoveArgs 60 | 61 | Log-Group "Archiving ${ProductName}..." 62 | $CompressArgs = @{ 63 | Path = (Get-ChildItem -Path "${ProjectRoot}/release/${Configuration}" -Exclude "${OutputName}*.*") 64 | CompressionLevel = 'Optimal' 65 | DestinationPath = "${ProjectRoot}/release/${OutputName}.zip" 66 | Verbose = ($Env:CI -ne $null) 67 | } 68 | Compress-Archive -Force @CompressArgs 69 | Log-Group 70 | } 71 | 72 | Package 73 | -------------------------------------------------------------------------------- /.github/actions/run-clang-format/action.yaml: -------------------------------------------------------------------------------- 1 | name: Run clang-format 2 | description: Runs clang-format and checks for any changes introduced by it 3 | inputs: 4 | failCondition: 5 | description: Controls whether failed checks also fail the workflow run 6 | required: false 7 | default: never 8 | workingDirectory: 9 | description: Working directory for checks 10 | required: false 11 | default: ${{ github.workspace }} 12 | runs: 13 | using: composite 14 | steps: 15 | - name: Check Runner Operating System 🏃‍♂️ 16 | if: runner.os == 'Windows' 17 | shell: bash 18 | run: | 19 | : Check Runner Operating System 🏃‍♂️ 20 | echo "::notice::run-clang-format action requires a macOS-based or Linux-based runner." 21 | exit 2 22 | 23 | - name: Check for Changed Files ✅ 24 | uses: ./.github/actions/check-changes 25 | id: checks 26 | with: 27 | checkGlob: "'*.c' '*.h' '*.cpp' '*.hpp' '*.m' '*.mm'" 28 | diffFilter: 'ACM' 29 | 30 | - name: Install Dependencies 🛍️ 31 | if: runner.os == 'Linux' && fromJSON(steps.checks.outputs.hasChangedFiles) 32 | shell: bash 33 | run: | 34 | : Install Dependencies 🛍️ 35 | echo ::group::Install Dependencies 36 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 37 | echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH 38 | echo "/home/linuxbrew/.linuxbrew/opt/clang-format@19/bin" >> $GITHUB_PATH 39 | brew install --quiet zsh 40 | echo ::endgroup:: 41 | 42 | - name: Run clang-format 🐉 43 | if: fromJSON(steps.checks.outputs.hasChangedFiles) 44 | id: result 45 | shell: zsh --no-rcs --errexit --pipefail {0} 46 | working-directory: ${{ inputs.workingDirectory }} 47 | env: 48 | CHANGED_FILES: ${{ steps.checks.outputs.changedFiles }} 49 | run: | 50 | : Run clang-format 🐉 51 | if (( ${+RUNNER_DEBUG} )) setopt XTRACE 52 | 53 | print ::group::Install clang-format-19 54 | brew install --quiet obsproject/tools/clang-format@19 55 | print ::endgroup:: 56 | 57 | print ::group::Run clang-format-19 58 | local -a changes=(${(s:,:)CHANGED_FILES//[\[\]\'\"]/}) 59 | ./build-aux/run-clang-format --fail-${{ inputs.failCondition }} --check ${changes} 60 | print ::endgroup:: 61 | -------------------------------------------------------------------------------- /cmake/common/compiler_common.cmake: -------------------------------------------------------------------------------- 1 | # CMake common compiler options module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # Set C and C++ language standards to C17 and C++17 6 | set(CMAKE_C_STANDARD 17) 7 | set(CMAKE_C_STANDARD_REQUIRED TRUE) 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 10 | 11 | # Set symbols to be hidden by default for C and C++ 12 | set(CMAKE_C_VISIBILITY_PRESET hidden) 13 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 14 | set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE) 15 | 16 | # clang options for C, C++, ObjC, and ObjC++ 17 | set(_obs_clang_common_options 18 | -fno-strict-aliasing 19 | -Wno-trigraphs 20 | -Wno-missing-field-initializers 21 | -Wno-missing-prototypes 22 | -Werror=return-type 23 | -Wunreachable-code 24 | -Wquoted-include-in-framework-header 25 | -Wno-missing-braces 26 | -Wparentheses 27 | -Wswitch 28 | -Wno-unused-function 29 | -Wno-unused-label 30 | -Wunused-parameter 31 | -Wunused-variable 32 | -Wunused-value 33 | -Wempty-body 34 | -Wuninitialized 35 | -Wno-unknown-pragmas 36 | -Wfour-char-constants 37 | -Wconstant-conversion 38 | -Wno-conversion 39 | -Wint-conversion 40 | -Wbool-conversion 41 | -Wenum-conversion 42 | -Wnon-literal-null-conversion 43 | -Wsign-compare 44 | -Wshorten-64-to-32 45 | -Wpointer-sign 46 | -Wnewline-eof 47 | -Wno-implicit-fallthrough 48 | -Wdeprecated-declarations 49 | -Wno-sign-conversion 50 | -Winfinite-recursion 51 | -Wcomma 52 | -Wno-strict-prototypes 53 | -Wno-semicolon-before-method-body 54 | -Wformat-security 55 | -Wvla 56 | -Wno-error=shorten-64-to-32 57 | ) 58 | 59 | # clang options for C 60 | set(_obs_clang_c_options 61 | ${_obs_clang_common_options} 62 | -Wno-shadow 63 | -Wno-float-conversion 64 | ) 65 | 66 | # clang options for C++ 67 | set(_obs_clang_cxx_options 68 | ${_obs_clang_common_options} 69 | -Wno-non-virtual-dtor 70 | -Wno-overloaded-virtual 71 | -Wno-exit-time-destructors 72 | -Wno-shadow 73 | -Winvalid-offsetof 74 | -Wmove 75 | -Werror=block-capture-autoreleasing 76 | -Wrange-loop-analysis 77 | ) 78 | 79 | if(CMAKE_CXX_STANDARD GREATER_EQUAL 20) 80 | list(APPEND _obs_clang_cxx_options -fno-char8_t) 81 | endif() 82 | 83 | if(NOT DEFINED CMAKE_COMPILE_WARNING_AS_ERROR) 84 | set(CMAKE_COMPILE_WARNING_AS_ERROR ON) 85 | endif() 86 | -------------------------------------------------------------------------------- /.github/scripts/Build-Windows.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param( 3 | [ValidateSet('x64')] 4 | [string] $Target = 'x64', 5 | [ValidateSet('Debug', 'RelWithDebInfo', 'Release', 'MinSizeRel')] 6 | [string] $Configuration = 'RelWithDebInfo' 7 | ) 8 | 9 | $ErrorActionPreference = 'Stop' 10 | 11 | if ( $DebugPreference -eq 'Continue' ) { 12 | $VerbosePreference = 'Continue' 13 | $InformationPreference = 'Continue' 14 | } 15 | 16 | if ( $env:CI -eq $null ) { 17 | throw "Build-Windows.ps1 requires CI environment" 18 | } 19 | 20 | if ( ! ( [System.Environment]::Is64BitOperatingSystem ) ) { 21 | throw "A 64-bit system is required to build the project." 22 | } 23 | 24 | if ( $PSVersionTable.PSVersion -lt '7.2.0' ) { 25 | Write-Warning 'The obs-studio PowerShell build script requires PowerShell Core 7. Install or upgrade your PowerShell version: https://aka.ms/pscore6' 26 | exit 2 27 | } 28 | 29 | function Build { 30 | trap { 31 | Pop-Location -Stack BuildTemp -ErrorAction 'SilentlyContinue' 32 | Write-Error $_ 33 | Log-Group 34 | exit 2 35 | } 36 | 37 | $ScriptHome = $PSScriptRoot 38 | $ProjectRoot = Resolve-Path -Path "$PSScriptRoot/../.." 39 | 40 | $UtilityFunctions = Get-ChildItem -Path $PSScriptRoot/utils.pwsh/*.ps1 -Recurse 41 | 42 | foreach($Utility in $UtilityFunctions) { 43 | Write-Debug "Loading $($Utility.FullName)" 44 | . $Utility.FullName 45 | } 46 | 47 | Push-Location -Stack BuildTemp 48 | Ensure-Location $ProjectRoot 49 | 50 | $CmakeArgs = @('--preset', "windows-ci-${Target}") 51 | $CmakeBuildArgs = @('--build') 52 | $CmakeInstallArgs = @() 53 | 54 | if ( $DebugPreference -eq 'Continue' ) { 55 | $CmakeArgs += ('--debug-output') 56 | $CmakeBuildArgs += ('--verbose') 57 | $CmakeInstallArgs += ('--verbose') 58 | } 59 | 60 | $CmakeBuildArgs += @( 61 | '--preset', "windows-${Target}" 62 | '--config', $Configuration 63 | '--parallel' 64 | '--', '/consoleLoggerParameters:Summary', '/noLogo' 65 | ) 66 | 67 | $CmakeInstallArgs += @( 68 | '--install', "build_${Target}" 69 | '--prefix', "${ProjectRoot}/release/${Configuration}" 70 | '--config', $Configuration 71 | ) 72 | 73 | Log-Group "Configuring ${ProductName}..." 74 | Invoke-External cmake @CmakeArgs 75 | 76 | Log-Group "Building ${ProductName}..." 77 | Invoke-External cmake @CmakeBuildArgs 78 | 79 | Log-Group "Installing ${ProductName}..." 80 | Invoke-External cmake @CmakeInstallArgs 81 | 82 | Pop-Location -Stack BuildTemp 83 | Log-Group 84 | } 85 | 86 | Build 87 | -------------------------------------------------------------------------------- /cmake/common/bootstrap.cmake: -------------------------------------------------------------------------------- 1 | # Plugin bootstrap module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # Map fallback configurations for optimized build configurations 6 | # gersemi: off 7 | set( 8 | CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO 9 | RelWithDebInfo 10 | Release 11 | MinSizeRel 12 | None 13 | "" 14 | ) 15 | set( 16 | CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL 17 | MinSizeRel 18 | Release 19 | RelWithDebInfo 20 | None 21 | "" 22 | ) 23 | set( 24 | CMAKE_MAP_IMPORTED_CONFIG_RELEASE 25 | Release 26 | RelWithDebInfo 27 | MinSizeRel 28 | None 29 | "" 30 | ) 31 | # gersemi: on 32 | 33 | # Prohibit in-source builds 34 | if("${CMAKE_CURRENT_BINARY_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") 35 | message( 36 | FATAL_ERROR 37 | "In-source builds are not supported. " 38 | "Specify a build directory via 'cmake -S -B ' instead." 39 | ) 40 | file( 41 | REMOVE_RECURSE 42 | "${CMAKE_CURRENT_SOURCE_DIR}/CMakeCache.txt" 43 | "${CMAKE_CURRENT_SOURCE_DIR}/CMakeFiles" 44 | ) 45 | endif() 46 | 47 | # Add common module directories to default search path 48 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/common") 49 | 50 | file(READ "${CMAKE_CURRENT_SOURCE_DIR}/buildspec.json" buildspec) 51 | 52 | string(JSON _name GET ${buildspec} name) 53 | string(JSON _website GET ${buildspec} website) 54 | string(JSON _author GET ${buildspec} author) 55 | string(JSON _email GET ${buildspec} email) 56 | string(JSON _version GET ${buildspec} version) 57 | string(JSON _bundleId GET ${buildspec} platformConfig macos bundleId) 58 | 59 | set(PLUGIN_AUTHOR ${_author}) 60 | set(PLUGIN_WEBSITE ${_website}) 61 | set(PLUGIN_EMAIL ${_email}) 62 | set(PLUGIN_VERSION ${_version}) 63 | set(MACOS_BUNDLEID ${_bundleId}) 64 | 65 | string(REPLACE "." ";" _version_canonical "${_version}") 66 | list(GET _version_canonical 0 PLUGIN_VERSION_MAJOR) 67 | list(GET _version_canonical 1 PLUGIN_VERSION_MINOR) 68 | list(GET _version_canonical 2 PLUGIN_VERSION_PATCH) 69 | unset(_version_canonical) 70 | 71 | include(buildnumber) 72 | include(osconfig) 73 | 74 | # Allow selection of common build types via UI 75 | if(NOT CMAKE_GENERATOR MATCHES "(Xcode|Visual Studio .+)") 76 | if(NOT CMAKE_BUILD_TYPE) 77 | set(CMAKE_BUILD_TYPE 78 | "RelWithDebInfo" 79 | CACHE STRING 80 | "OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]" 81 | FORCE 82 | ) 83 | set_property( 84 | CACHE CMAKE_BUILD_TYPE 85 | PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel 86 | ) 87 | endif() 88 | endif() 89 | 90 | # Disable exports automatically going into the CMake package registry 91 | set(CMAKE_EXPORT_PACKAGE_REGISTRY FALSE) 92 | # Enable default inclusion of targets' source and binary directory 93 | set(CMAKE_INCLUDE_CURRENT_DIR TRUE) 94 | -------------------------------------------------------------------------------- /.github/actions/check-changes/action.yaml: -------------------------------------------------------------------------------- 1 | name: Check For Changed Files 2 | description: Checks for changed files compared to specific git reference and glob expression 3 | inputs: 4 | baseRef: 5 | description: Git reference to check against 6 | required: false 7 | ref: 8 | description: Git reference to check with 9 | required: false 10 | default: HEAD 11 | checkGlob: 12 | description: Glob expression to limit check to specific files 13 | required: false 14 | useFallback: 15 | description: Use fallback compare against prior commit 16 | required: false 17 | default: 'true' 18 | diffFilter: 19 | description: git diff-filter string to use 20 | required: false 21 | default: '' 22 | outputs: 23 | hasChangedFiles: 24 | value: ${{ steps.checks.outputs.hasChangedFiles }} 25 | description: True if specified files were changed in comparison to specified git reference 26 | changedFiles: 27 | value: ${{ steps.checks.outputs.changedFiles }} 28 | description: List of changed files 29 | runs: 30 | using: composite 31 | steps: 32 | - name: Check For Changed Files ✅ 33 | shell: bash 34 | id: checks 35 | env: 36 | GIT_BASE_REF: ${{ inputs.baseRef }} 37 | GIT_REF: ${{ inputs.ref }} 38 | GITHUB_EVENT_FORCED: ${{ github.event.forced }} 39 | GITHUB_REF_BEFORE: ${{ github.event.before }} 40 | USE_FALLBACK: ${{ inputs.useFallback }} 41 | DIFF_FILTER: ${{ inputs.diffFilter }} 42 | run: | 43 | : Check for Changed Files ✅ 44 | if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi 45 | shopt -s extglob 46 | shopt -s dotglob 47 | 48 | if [[ "${GIT_BASE_REF}" ]]; then 49 | if ! git cat-file -e "${GIT_BASE_REF}" &> /dev/null; then 50 | echo "::warning::Provided base reference ${GIT_BASE_REF} is invalid" 51 | if [[ "${USE_FALLBACK}" == 'true' ]]; then 52 | GIT_BASE_REF='HEAD~1' 53 | fi 54 | fi 55 | else 56 | if ! git cat-file -e ${GITHUB_REF_BEFORE} &> /dev/null; then 57 | GITHUB_REF_BEFORE='4b825dc642cb6eb9a060e54bf8d69288fbee4904' 58 | fi 59 | 60 | GIT_BASE_REF='HEAD~1' 61 | case "${GITHUB_EVENT_NAME}" in 62 | pull_request) GIT_BASE_REF="origin/${GITHUB_BASE_REF}" ;; 63 | push) if [[ "${GITHUB_EVENT_FORCED}" != 'true' ]]; then GIT_BASE_REF="${GITHUB_REF_BEFORE}"; fi ;; 64 | *) ;; 65 | esac 66 | fi 67 | 68 | changes=($(git diff --name-only --diff-filter="${DIFF_FILTER}" ${GIT_BASE_REF} ${GIT_REF} -- ${{ inputs.checkGlob }})) 69 | 70 | if (( ${#changes[@]} )); then 71 | file_string="${changes[*]}" 72 | echo "hasChangedFiles=true" >> $GITHUB_OUTPUT 73 | echo "changedFiles=[\"${file_string// /\",\"}\"]" >> $GITHUB_OUTPUT 74 | else 75 | echo "hasChangedFiles=false" >> $GITHUB_OUTPUT 76 | echo "changedFiles=[]" >> GITHUB_OUTPUT 77 | fi 78 | -------------------------------------------------------------------------------- /cmake/linux/compilerconfig.cmake: -------------------------------------------------------------------------------- 1 | # CMake Linux compiler configuration module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(ccache) 6 | include(compiler_common) 7 | 8 | option( 9 | ENABLE_COMPILER_TRACE 10 | "Enable Clang time-trace (required Clang and Ninja)" 11 | OFF 12 | ) 13 | mark_as_advanced(ENABLE_COMPILER_TRACE) 14 | 15 | # gcc options for C 16 | set(_obs_gcc_c_options 17 | -fno-strict-aliasing 18 | -fopenmp-simd 19 | -Wdeprecated-declarations 20 | -Wempty-body 21 | -Wenum-conversion 22 | -Werror=return-type 23 | -Wextra 24 | -Wformat 25 | -Wformat-security 26 | -Wno-conversion 27 | -Wno-float-conversion 28 | -Wno-implicit-fallthrough 29 | -Wno-missing-braces 30 | -Wno-missing-field-initializers 31 | -Wno-shadow 32 | -Wno-sign-conversion 33 | -Wno-trigraphs 34 | -Wno-unknown-pragmas 35 | -Wno-unused-function 36 | -Wno-unused-label 37 | -Wparentheses 38 | -Wuninitialized 39 | -Wunreachable-code 40 | -Wunused-parameter 41 | -Wunused-value 42 | -Wunused-variable 43 | -Wvla 44 | ) 45 | 46 | add_compile_options( 47 | -fopenmp-simd 48 | "$<$:${_obs_gcc_c_options}>" 49 | "$<$:-Wint-conversion;-Wno-missing-prototypes;-Wno-strict-prototypes;-Wpointer-sign>" 50 | "$<$:${_obs_gcc_c_options}>" 51 | "$<$:-Winvalid-offsetof;-Wno-overloaded-virtual>" 52 | "$<$:${_obs_clang_c_options}>" 53 | "$<$:${_obs_clang_cxx_options}>" 54 | ) 55 | 56 | if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) 57 | # * Disable false-positive warning in GCC 12.1.0 and later 58 | # * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105562 59 | if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.1.0) 60 | add_compile_options(-Wno-error=maybe-uninitialized) 61 | endif() 62 | 63 | # * Add warning for infinite recursion (added in GCC 12) 64 | # * Also disable warnings for stringop-overflow due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106297 65 | if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0) 66 | add_compile_options(-Winfinite-recursion -Wno-stringop-overflow) 67 | endif() 68 | 69 | if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64) 70 | add_compile_options(-Wno-error=type-limits) 71 | endif() 72 | endif() 73 | 74 | # Enable compiler and build tracing (requires Ninja generator) 75 | if(ENABLE_COMPILER_TRACE AND CMAKE_GENERATOR STREQUAL "Ninja") 76 | add_compile_options( 77 | $<$:-ftime-trace> 78 | $<$:-ftime-trace> 79 | ) 80 | else() 81 | set(ENABLE_COMPILER_TRACE 82 | OFF 83 | CACHE STRING 84 | "Enable Clang time-trace (required Clang and Ninja)" 85 | FORCE 86 | ) 87 | endif() 88 | 89 | add_compile_definitions( 90 | $<$:DEBUG> 91 | $<$:_DEBUG> 92 | SIMDE_ENABLE_OPENMP 93 | ) 94 | -------------------------------------------------------------------------------- /src/lowerthirdswitcher-widget.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LOWERTHIRDSWITCHERDOCKWIDGET_H 2 | #define LOWERTHIRDSWITCHERDOCKWIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include "ui_LowerThirdSwitcher.h" 38 | 39 | #define CONFIG "config.json" 40 | 41 | class LowerthirdswitcherDockWidget : public QWidget { 42 | Q_OBJECT 43 | public: 44 | explicit LowerthirdswitcherDockWidget(QWidget *parent = nullptr); 45 | ~LowerthirdswitcherDockWidget(); 46 | 47 | int nextButtonHotkeyId = -1; 48 | 49 | struct lowerthirditem { 50 | QString mainText; 51 | QString secondaryText; 52 | }; 53 | 54 | private: 55 | enum SourceType { TEXT_SOURCE = 1, GROUP_SOURCE = 2, SCENE_SOURCE = 3 }; 56 | 57 | Ui::LowerThirdSwitcher *ui; 58 | 59 | static void OBSSourceCreated(void *param, calldata_t *calldata); 60 | static void OBSSourceDeleted(void *param, calldata_t *calldata); 61 | static void OBSSourceRenamed(void *param, calldata_t *calldata); 62 | 63 | static void OBSFrontendEventHandler(enum obs_frontend_event event, 64 | void *private_data); 65 | 66 | static int CheckSourceType(obs_source_t *source); 67 | static void LoadSavedSettings(Ui::LowerThirdSwitcher *ui, 68 | LowerthirdswitcherDockWidget *context); 69 | 70 | // Helper functions for Studio Mode operations 71 | bool IsProgramSceneSelected(); 72 | void SetPreviewToSelectedScene(); 73 | void DisableSwapScenesMode(bool &wasEnabled); 74 | void RestoreSwapScenesMode(bool wasEnabled); 75 | void TriggerTransitionWithSwapDisabled(); 76 | 77 | private slots: 78 | void ConnectObsSignalHandlers(); 79 | void DisonnectObsSignalHandlers(); 80 | void ConnectUISignalHandlers(); 81 | 82 | void SaveSettings(); 83 | 84 | void RegisterHotkeys(LowerthirdswitcherDockWidget *context); 85 | void UnregisterHotkeys(); 86 | 87 | void sceneChanged(QString scene); 88 | void groupSourceChanged(QString newText); 89 | void mainTextSourceChanged(QString newText); 90 | void secondaryTextSourceChanged(QString newText); 91 | void displayTimeValueChanged(int newTime); 92 | 93 | void nextItem(); 94 | 95 | void addNewItemClicked(); 96 | void setActiveItemClicked(); 97 | void deleteItemClicked(); 98 | void editItemClicked(QListWidgetItem *listWidgetItem); 99 | 100 | void mainTextEdited(QString newText); 101 | void secondaryTextEdited(QString newText); 102 | 103 | void LoadItemsToList(); 104 | 105 | void setActiveItem(int i); 106 | void setCurrentSceneCollection(); 107 | const char *getCurrentSceneCollection(); 108 | }; 109 | 110 | #endif // LOWERTHIRDSWITCHERDOCKWIDGET_H 111 | -------------------------------------------------------------------------------- /cmake/linux/defaults.cmake: -------------------------------------------------------------------------------- 1 | # CMake Linux defaults module 2 | 3 | include_guard(GLOBAL) 4 | 5 | # Set default installation directories 6 | include(GNUInstallDirs) 7 | 8 | if(CMAKE_INSTALL_LIBDIR MATCHES "(CMAKE_SYSTEM_PROCESSOR)") 9 | string( 10 | REPLACE 11 | "CMAKE_SYSTEM_PROCESSOR" 12 | "${CMAKE_SYSTEM_PROCESSOR}" 13 | CMAKE_INSTALL_LIBDIR 14 | "${CMAKE_INSTALL_LIBDIR}" 15 | ) 16 | endif() 17 | 18 | # Enable find_package targets to become globally available targets 19 | set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE) 20 | 21 | set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}") 22 | set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}") 23 | set(CPACK_PACKAGE_FILE_NAME 24 | "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_C_LIBRARY_ARCHITECTURE}" 25 | ) 26 | 27 | set(CPACK_GENERATOR "DEB") 28 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 29 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PLUGIN_EMAIL}") 30 | set(CPACK_SET_DESTDIR ON) 31 | 32 | if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25.0 OR NOT CMAKE_CROSSCOMPILING) 33 | set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON) 34 | endif() 35 | 36 | set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/release") 37 | 38 | set(CPACK_SOURCE_GENERATOR "TXZ") 39 | set(CPACK_SOURCE_IGNORE_FILES 40 | ".*~$" 41 | \\.git/ 42 | \\.github/ 43 | \\.gitignore 44 | \\.ccache/ 45 | build_.* 46 | cmake/\\.CMakeBuildNumber 47 | release/ 48 | ) 49 | 50 | set(CPACK_VERBATIM_VARIABLES YES) 51 | set(CPACK_SOURCE_PACKAGE_FILE_NAME 52 | "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-source" 53 | ) 54 | set(CPACK_ARCHIVE_THREADS 0) 55 | 56 | include(CPack) 57 | 58 | find_package(libobs QUIET) 59 | 60 | if(NOT TARGET OBS::libobs) 61 | find_package(LibObs REQUIRED) 62 | add_library(OBS::libobs ALIAS libobs) 63 | 64 | if(ENABLE_FRONTEND_API) 65 | find_path( 66 | obs-frontend-api_INCLUDE_DIR 67 | NAMES obs-frontend-api.h 68 | PATHS /usr/include /usr/local/include 69 | PATH_SUFFIXES obs 70 | ) 71 | 72 | find_library( 73 | obs-frontend-api_LIBRARY 74 | NAMES obs-frontend-api 75 | PATHS /usr/lib /usr/local/lib 76 | ) 77 | 78 | if(obs-frontend-api_LIBRARY) 79 | if(NOT TARGET OBS::obs-frontend-api) 80 | if(IS_ABSOLUTE "${obs-frontend-api_LIBRARY}") 81 | add_library(OBS::obs-frontend-api UNKNOWN IMPORTED) 82 | set_property( 83 | TARGET OBS::obs-frontend-api 84 | PROPERTY IMPORTED_LOCATION "${obs-frontend-api_LIBRARY}" 85 | ) 86 | else() 87 | add_library(OBS::obs-frontend-api INTERFACE IMPORTED) 88 | set_property( 89 | TARGET OBS::obs-frontend-api 90 | PROPERTY IMPORTED_LIBNAME "${obs-frontend-api_LIBRARY}" 91 | ) 92 | endif() 93 | 94 | set_target_properties( 95 | OBS::obs-frontend-api 96 | PROPERTIES 97 | INTERFACE_INCLUDE_DIRECTORIES 98 | "${obs-frontend-api_INCLUDE_DIR}" 99 | ) 100 | endif() 101 | endif() 102 | endif() 103 | 104 | macro(find_package) 105 | if( 106 | NOT "${ARGV0}" STREQUAL libobs 107 | AND NOT "${ARGV0}" STREQUAL obs-frontend-api 108 | ) 109 | _find_package(${ARGV}) 110 | endif() 111 | endmacro() 112 | endif() 113 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # please use clang-format version 8 or later 2 | 3 | Standard: Cpp11 4 | AccessModifierOffset: -8 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | #AllowAllArgumentsOnNextLine: false # requires clang-format 9 12 | #AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: false 18 | #AllowShortLambdasOnASingleLine: Inline # requires clang-format 9 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: false 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BraceWrapping: 27 | AfterClass: false 28 | AfterControlStatement: false 29 | AfterEnum: false 30 | AfterFunction: true 31 | AfterNamespace: false 32 | AfterObjCDeclaration: false 33 | AfterStruct: false 34 | AfterUnion: false 35 | AfterExternBlock: false 36 | BeforeCatch: false 37 | BeforeElse: false 38 | IndentBraces: false 39 | SplitEmptyFunction: true 40 | SplitEmptyRecord: true 41 | SplitEmptyNamespace: true 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Custom 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializers: BeforeColon 46 | BreakStringLiterals: false # apparently unpredictable 47 | ColumnLimit: 80 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 8 51 | ContinuationIndentWidth: 8 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | FixNamespaceComments: false 56 | ForEachMacros: 57 | - 'json_object_foreach' 58 | - 'json_object_foreach_safe' 59 | - 'json_array_foreach' 60 | IncludeBlocks: Preserve 61 | IndentCaseLabels: false 62 | IndentPPDirectives: None 63 | IndentWidth: 8 64 | IndentWrappedFunctionNames: false 65 | KeepEmptyLinesAtTheStartOfBlocks: true 66 | MaxEmptyLinesToKeep: 1 67 | NamespaceIndentation: None 68 | #ObjCBinPackProtocolList: Auto # requires clang-format 7 69 | ObjCBlockIndentWidth: 8 70 | ObjCSpaceAfterProperty: true 71 | ObjCSpaceBeforeProtocolList: true 72 | 73 | PenaltyBreakAssignment: 10 74 | PenaltyBreakBeforeFirstCallParameter: 30 75 | PenaltyBreakComment: 10 76 | PenaltyBreakFirstLessLess: 0 77 | PenaltyBreakString: 10 78 | PenaltyExcessCharacter: 100 79 | PenaltyReturnTypeOnItsOwnLine: 60 80 | 81 | PointerAlignment: Right 82 | ReflowComments: false 83 | SortIncludes: false 84 | SortUsingDeclarations: false 85 | SpaceAfterCStyleCast: false 86 | #SpaceAfterLogicalNot: false # requires clang-format 9 87 | SpaceAfterTemplateKeyword: false 88 | SpaceBeforeAssignmentOperators: true 89 | #SpaceBeforeCtorInitializerColon: true # requires clang-format 7 90 | #SpaceBeforeInheritanceColon: true # requires clang-format 7 91 | SpaceBeforeParens: ControlStatements 92 | #SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7 93 | SpaceInEmptyParentheses: false 94 | SpacesBeforeTrailingComments: 1 95 | SpacesInAngles: false 96 | SpacesInCStyleCastParentheses: false 97 | SpacesInContainerLiterals: false 98 | SpacesInParentheses: false 99 | SpacesInSquareBrackets: false 100 | #StatementMacros: # requires clang-format 8 101 | # - 'Q_OBJECT' 102 | TabWidth: 8 103 | #TypenameMacros: # requires clang-format 9 104 | # - 'DARRAY' 105 | UseTab: ForContinuationAndIndentation 106 | --- 107 | Language: ObjC 108 | -------------------------------------------------------------------------------- /.github/actions/build-plugin/action.yaml: -------------------------------------------------------------------------------- 1 | name: Set up and build plugin 2 | description: Builds the plugin for specified architecture and build config 3 | inputs: 4 | target: 5 | description: Target architecture for dependencies 6 | required: true 7 | config: 8 | description: Build configuration 9 | required: false 10 | default: RelWithDebInfo 11 | codesign: 12 | description: Enable codesigning (macOS only) 13 | required: false 14 | default: 'false' 15 | codesignIdent: 16 | description: Developer ID for application codesigning (macOS only) 17 | required: false 18 | default: '-' 19 | codesignTeam: 20 | description: Team ID for application codesigning (macOS only) 21 | required: false 22 | default: '' 23 | workingDirectory: 24 | description: Working directory for packaging 25 | required: false 26 | default: ${{ github.workspace }} 27 | runs: 28 | using: composite 29 | steps: 30 | - name: Run macOS Build 31 | if: runner.os == 'macOS' 32 | shell: zsh --no-rcs --errexit --pipefail {0} 33 | working-directory: ${{ inputs.workingDirectory }} 34 | env: 35 | CCACHE_DIR: ${{ inputs.workingDirectory }}/.ccache 36 | CODESIGN_IDENT: ${{ inputs.codesignIdent }} 37 | CODESIGN_TEAM: ${{ inputs.codesignTeam }} 38 | run: | 39 | : Run macOS Build 40 | 41 | local -a build_args=(--config ${{ inputs.config }}) 42 | if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) 43 | 44 | if [[ '${{ inputs.codesign }}' == 'true' ]] build_args+=(--codesign) 45 | 46 | .github/scripts/build-macos ${build_args} 47 | 48 | - name: Install Dependencies 🛍️ 49 | if: runner.os == 'Linux' 50 | shell: bash 51 | run: | 52 | : Install Dependencies 🛍️ 53 | echo ::group::Install Dependencies 54 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 55 | echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH 56 | brew install --quiet zsh 57 | echo ::endgroup:: 58 | 59 | - name: Run Ubuntu Build 60 | if: runner.os == 'Linux' 61 | shell: zsh --no-rcs --errexit --pipefail {0} 62 | working-directory: ${{ inputs.workingDirectory }} 63 | env: 64 | CCACHE_DIR: ${{ inputs.workingDirectory }}/.ccache 65 | run: | 66 | : Run Ubuntu Build 67 | 68 | local -a build_args=( 69 | --target ubuntu-${{ inputs.target }} 70 | --config ${{ inputs.config }} 71 | ) 72 | if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) 73 | 74 | .github/scripts/build-ubuntu ${build_args} 75 | 76 | - name: Run Windows Build 77 | if: runner.os == 'Windows' 78 | shell: pwsh 79 | run: | 80 | # Run Windows Build 81 | if ( $Env:RUNNER_DEBUG -ne $null ) { 82 | Set-PSDebug -Trace 1 83 | } 84 | 85 | $BuildArgs = @{ 86 | Target = '${{ inputs.target }}' 87 | Configuration = '${{ inputs.config }}' 88 | } 89 | 90 | .github/scripts/Build-Windows.ps1 @BuildArgs 91 | 92 | - name: Create Summary 📊 93 | if: contains(fromJSON('["Linux", "macOS"]'),runner.os) 94 | shell: zsh --no-rcs --errexit --pipefail {0} 95 | env: 96 | CCACHE_DIR: ${{ inputs.workingDirectory }}/.ccache 97 | run: | 98 | : Create Summary 📊 99 | 100 | local -a ccache_data 101 | if (( ${+RUNNER_DEBUG} )) { 102 | setopt XTRACE 103 | ccache_data=("${(fA)$(ccache -s -vv)}") 104 | } else { 105 | ccache_data=("${(fA)$(ccache -s)}") 106 | } 107 | 108 | print '### ${{ runner.os }} Ccache Stats (${{ inputs.target }})' >> $GITHUB_STEP_SUMMARY 109 | print '```' >> $GITHUB_STEP_SUMMARY 110 | for line (${ccache_data}) { 111 | print ${line} >> $GITHUB_STEP_SUMMARY 112 | } 113 | print '```' >> $GITHUB_STEP_SUMMARY 114 | -------------------------------------------------------------------------------- /cmake/macos/compilerconfig.cmake: -------------------------------------------------------------------------------- 1 | # CMake macOS compiler configuration module 2 | 3 | include_guard(GLOBAL) 4 | 5 | option(ENABLE_COMPILER_TRACE "Enable clang time-trace" OFF) 6 | mark_as_advanced(ENABLE_COMPILER_TRACE) 7 | 8 | if(NOT XCODE) 9 | message( 10 | FATAL_ERROR 11 | "Building OBS Studio on macOS requires Xcode generator." 12 | ) 13 | endif() 14 | 15 | include(ccache) 16 | include(compiler_common) 17 | 18 | add_compile_options("$<$>:-fopenmp-simd>") 19 | 20 | # Ensure recent enough Xcode and platform SDK 21 | function(check_sdk_requirements) 22 | set(obs_macos_minimum_sdk 15.0) # Keep in sync with Xcode 23 | set(obs_macos_minimum_xcode 16.0) # Keep in sync with SDK 24 | execute_process( 25 | COMMAND xcrun --sdk macosx --show-sdk-platform-version 26 | OUTPUT_VARIABLE obs_macos_current_sdk 27 | RESULT_VARIABLE result 28 | OUTPUT_STRIP_TRAILING_WHITESPACE 29 | ) 30 | if(NOT result EQUAL 0) 31 | message( 32 | FATAL_ERROR 33 | "Failed to fetch macOS SDK version. " 34 | "Ensure that the macOS SDK is installed and that xcode-select points at the Xcode developer directory." 35 | ) 36 | endif() 37 | message(DEBUG "macOS SDK version: ${obs_macos_current_sdk}") 38 | if(obs_macos_current_sdk VERSION_LESS obs_macos_minimum_sdk) 39 | message( 40 | FATAL_ERROR 41 | "Your macOS SDK version (${obs_macos_current_sdk}) is too low. " 42 | "The macOS ${obs_macos_minimum_sdk} SDK (Xcode ${obs_macos_minimum_xcode}) is required to build OBS." 43 | ) 44 | endif() 45 | execute_process( 46 | COMMAND xcrun --find xcodebuild 47 | OUTPUT_VARIABLE obs_macos_xcodebuild 48 | RESULT_VARIABLE result 49 | ) 50 | if(NOT result EQUAL 0) 51 | message( 52 | FATAL_ERROR 53 | "Xcode was not found. " 54 | "Ensure you have installed Xcode and that xcode-select points at the Xcode developer directory." 55 | ) 56 | endif() 57 | message(DEBUG "Path to xcodebuild binary: ${obs_macos_xcodebuild}") 58 | if(XCODE_VERSION VERSION_LESS obs_macos_minimum_xcode) 59 | message( 60 | FATAL_ERROR 61 | "Your Xcode version (${XCODE_VERSION}) is too low. Xcode ${obs_macos_minimum_xcode} is required to build OBS." 62 | ) 63 | endif() 64 | endfunction() 65 | 66 | check_sdk_requirements() 67 | 68 | # Enable dSYM generator for release builds 69 | string(APPEND CMAKE_C_FLAGS_RELEASE " -g") 70 | string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g") 71 | string(APPEND CMAKE_OBJC_FLAGS_RELEASE " -g") 72 | string(APPEND CMAKE_OBJCXX_FLAGS_RELEASE " -g") 73 | 74 | # Default ObjC compiler options used by Xcode: 75 | # 76 | # * -Wno-implicit-atomic-properties 77 | # * -Wno-objc-interface-ivars 78 | # * -Warc-repeated-use-of-weak 79 | # * -Wno-arc-maybe-repeated-use-of-weak 80 | # * -Wimplicit-retain-self 81 | # * -Wduplicate-method-match 82 | # * -Wshadow 83 | # * -Wfloat-conversion 84 | # * -Wobjc-literal-conversion 85 | # * -Wno-selector 86 | # * -Wno-strict-selector-match 87 | # * -Wundeclared-selector 88 | # * -Wdeprecated-implementations 89 | # * -Wprotocol 90 | # * -Werror=block-capture-autoreleasing 91 | # * -Wrange-loop-analysis 92 | 93 | # Default ObjC++ compiler options used by Xcode: 94 | # 95 | # * -Wno-non-virtual-dtor 96 | 97 | add_compile_definitions( 98 | $<$>:$<$:DEBUG>> 99 | $<$>:$<$:_DEBUG>> 100 | $<$>:SIMDE_ENABLE_OPENMP> 101 | ) 102 | 103 | if(ENABLE_COMPILER_TRACE) 104 | add_compile_options( 105 | $<$>:-ftime-trace> 106 | "$<$:SHELL:-Xfrontend -debug-time-expression-type-checking>" 107 | "$<$:SHELL:-Xfrontend -debug-time-function-bodies>" 108 | ) 109 | add_link_options(LINKER:-print_statistics) 110 | endif() 111 | -------------------------------------------------------------------------------- /.github/actions/package-plugin/action.yaml: -------------------------------------------------------------------------------- 1 | name: Package plugin 2 | description: Packages the plugin for specified architecture and build config. 3 | inputs: 4 | target: 5 | description: Build target for dependencies 6 | required: true 7 | config: 8 | description: Build configuration 9 | required: false 10 | default: RelWithDebInfo 11 | codesign: 12 | description: Enable codesigning (macOS only) 13 | required: false 14 | default: 'false' 15 | notarize: 16 | description: Enable notarization (macOS only) 17 | required: false 18 | default: 'false' 19 | codesignIdent: 20 | description: Developer ID for application codesigning (macOS only) 21 | required: false 22 | default: '-' 23 | installerIdent: 24 | description: Developer ID for installer package codesigning (macOS only) 25 | required: false 26 | default: '' 27 | codesignTeam: 28 | description: Developer team for codesigning (macOS only) 29 | required: false 30 | default: '' 31 | codesignUser: 32 | description: Apple ID username for notarization (macOS only) 33 | required: false 34 | default: '' 35 | codesignPass: 36 | description: Apple ID password for notarization (macOS only) 37 | required: false 38 | default: '' 39 | package: 40 | description: Create Windows or macOS installation package 41 | required: false 42 | default: 'false' 43 | workingDirectory: 44 | description: Working directory for packaging 45 | required: false 46 | default: ${{ github.workspace }} 47 | runs: 48 | using: composite 49 | steps: 50 | - name: Run macOS Packaging 51 | if: runner.os == 'macOS' 52 | shell: zsh --no-rcs --errexit --pipefail {0} 53 | working-directory: ${{ inputs.workingDirectory }} 54 | env: 55 | CODESIGN_IDENT: ${{ inputs.codesignIdent }} 56 | CODESIGN_IDENT_INSTALLER: ${{ inputs.installerIdent }} 57 | CODESIGN_TEAM: ${{ inputs.codesignTeam }} 58 | CODESIGN_IDENT_USER: ${{ inputs.codesignUser }} 59 | CODESIGN_IDENT_PASS: ${{ inputs.codesignPass }} 60 | run: | 61 | : Run macOS Packaging 62 | 63 | local -a package_args=(--config ${{ inputs.config }}) 64 | if (( ${+RUNNER_DEBUG} )) package_args+=(--debug) 65 | 66 | if [[ '${{ inputs.codesign }}' == 'true' ]] package_args+=(--codesign) 67 | if [[ '${{ inputs.notarize }}' == 'true' ]] package_args+=(--notarize) 68 | if [[ '${{ inputs.package }}' == 'true' ]] package_args+=(--package) 69 | 70 | .github/scripts/package-macos ${package_args} 71 | 72 | - name: Install Dependencies 🛍️ 73 | if: runner.os == 'Linux' 74 | shell: bash 75 | run: | 76 | : Install Dependencies 🛍️ 77 | echo ::group::Install Dependencies 78 | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" 79 | echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH 80 | brew install --quiet zsh 81 | echo ::endgroup:: 82 | 83 | - name: Run Ubuntu Packaging 84 | if: runner.os == 'Linux' 85 | shell: zsh --no-rcs --errexit --pipefail {0} 86 | working-directory: ${{ inputs.workingDirectory }} 87 | run: | 88 | : Run Ubuntu Packaging 89 | package_args=( 90 | --target ubuntu-${{ inputs.target }} 91 | --config ${{ inputs.config }} 92 | ) 93 | if (( ${+RUNNER_DEBUG} )) build_args+=(--debug) 94 | 95 | if [[ '${{ inputs.package }}' == 'true' ]] package_args+=(--package) 96 | 97 | .github/scripts/package-ubuntu ${package_args} 98 | 99 | - name: Run Windows Packaging 100 | if: runner.os == 'Windows' 101 | shell: pwsh 102 | run: | 103 | # Run Windows Packaging 104 | if ( $Env:RUNNER_DEBUG -ne $null ) { 105 | Set-PSDebug -Trace 1 106 | } 107 | 108 | $PackageArgs = @{ 109 | Target = '${{ inputs.target }}' 110 | Configuration = '${{ inputs.config }}' 111 | } 112 | 113 | .github/scripts/Package-Windows.ps1 @PackageArgs 114 | -------------------------------------------------------------------------------- /.github/scripts/utils.pwsh/Logger.ps1: -------------------------------------------------------------------------------- 1 | function Log-Debug { 2 | [CmdletBinding()] 3 | param( 4 | [Parameter(Mandatory,ValueFromPipeline)] 5 | [ValidateNotNullOrEmpty()] 6 | [string[]] $Message 7 | ) 8 | 9 | Process { 10 | foreach($m in $Message) { 11 | Write-Debug "$(if ( $env:CI -ne $null ) { '::debug::' })$m" 12 | } 13 | } 14 | } 15 | 16 | function Log-Verbose { 17 | [CmdletBinding()] 18 | param( 19 | [Parameter(Mandatory,ValueFromPipeline)] 20 | [ValidateNotNullOrEmpty()] 21 | [string[]] $Message 22 | ) 23 | 24 | Process { 25 | foreach($m in $Message) { 26 | Write-Verbose $m 27 | } 28 | } 29 | } 30 | 31 | function Log-Warning { 32 | [CmdletBinding()] 33 | param( 34 | [Parameter(Mandatory,ValueFromPipeline)] 35 | [ValidateNotNullOrEmpty()] 36 | [string[]] $Message 37 | ) 38 | 39 | Process { 40 | foreach($m in $Message) { 41 | Write-Warning "$(if ( $env:CI -ne $null ) { '::warning::' })$m" 42 | } 43 | } 44 | } 45 | 46 | function Log-Error { 47 | [CmdletBinding()] 48 | param( 49 | [Parameter(Mandatory,ValueFromPipeline)] 50 | [ValidateNotNullOrEmpty()] 51 | [string[]] $Message 52 | ) 53 | 54 | Process { 55 | foreach($m in $Message) { 56 | Write-Error "$(if ( $env:CI -ne $null ) { '::error::' })$m" 57 | } 58 | } 59 | } 60 | 61 | function Log-Information { 62 | [CmdletBinding()] 63 | param( 64 | [Parameter(Mandatory,ValueFromPipeline)] 65 | [ValidateNotNullOrEmpty()] 66 | [string[]] $Message 67 | ) 68 | 69 | Process { 70 | if ( ! ( $script:Quiet ) ) { 71 | $StageName = $( if ( $script:StageName -ne $null ) { $script:StageName } else { '' }) 72 | $Icon = ' =>' 73 | 74 | foreach($m in $Message) { 75 | Write-Host -NoNewLine -ForegroundColor Blue " ${StageName} $($Icon.PadRight(5)) " 76 | Write-Host "${m}" 77 | } 78 | } 79 | } 80 | } 81 | 82 | function Log-Group { 83 | [CmdletBinding()] 84 | param( 85 | [Parameter(ValueFromPipeline)] 86 | [string[]] $Message 87 | ) 88 | 89 | Process { 90 | if ( $Env:CI -ne $null ) { 91 | if ( $script:LogGroup ) { 92 | Write-Output '::endgroup::' 93 | $script:LogGroup = $false 94 | } 95 | 96 | if ( $Message.count -ge 1 ) { 97 | Write-Output "::group::$($Message -join ' ')" 98 | $script:LogGroup = $true 99 | } 100 | } else { 101 | if ( $Message.count -ge 1 ) { 102 | Log-Information $Message 103 | } 104 | } 105 | } 106 | } 107 | 108 | function Log-Status { 109 | [CmdletBinding()] 110 | param( 111 | [Parameter(Mandatory,ValueFromPipeline)] 112 | [ValidateNotNullOrEmpty()] 113 | [string[]] $Message 114 | ) 115 | 116 | Process { 117 | if ( ! ( $script:Quiet ) ) { 118 | $StageName = $( if ( $StageName -ne $null ) { $StageName } else { '' }) 119 | $Icon = ' >' 120 | 121 | foreach($m in $Message) { 122 | Write-Host -NoNewLine -ForegroundColor Green " ${StageName} $($Icon.PadRight(5)) " 123 | Write-Host "${m}" 124 | } 125 | } 126 | } 127 | } 128 | 129 | function Log-Output { 130 | [CmdletBinding()] 131 | param( 132 | [Parameter(Mandatory,ValueFromPipeline)] 133 | [ValidateNotNullOrEmpty()] 134 | [string[]] $Message 135 | ) 136 | 137 | Process { 138 | if ( ! ( $script:Quiet ) ) { 139 | $StageName = $( if ( $script:StageName -ne $null ) { $script:StageName } else { '' }) 140 | $Icon = '' 141 | 142 | foreach($m in $Message) { 143 | Write-Output " ${StageName} $($Icon.PadRight(5)) ${m}" 144 | } 145 | } 146 | } 147 | } 148 | 149 | $Columns = (Get-Host).UI.RawUI.WindowSize.Width - 5 150 | -------------------------------------------------------------------------------- /.github/workflows/push.yaml: -------------------------------------------------------------------------------- 1 | name: Push 2 | run-name: ${{ github.ref_name }} push run 🚀 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - 'release/**' 9 | tags: 10 | - '*' 11 | permissions: 12 | contents: write 13 | jobs: 14 | check-format: 15 | name: Check Formatting 🔍 16 | if: github.ref_name == 'master' || github.ref_name == 'main' 17 | uses: ./.github/workflows/check-format.yaml 18 | permissions: 19 | contents: read 20 | 21 | build-project: 22 | name: Build Project 🧱 23 | uses: ./.github/workflows/build-project.yaml 24 | secrets: inherit 25 | permissions: 26 | contents: read 27 | 28 | create-release: 29 | name: Create Release 🛫 30 | if: github.ref_type == 'tag' 31 | runs-on: ubuntu-24.04 32 | needs: build-project 33 | defaults: 34 | run: 35 | shell: bash 36 | steps: 37 | - name: Check Release Tag ☑️ 38 | id: check 39 | run: | 40 | : Check Release Tag ☑️ 41 | if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi 42 | shopt -s extglob 43 | 44 | case "${GITHUB_REF_NAME}" in 45 | +([0-9]).+([0-9]).+([0-9]) ) 46 | echo 'validTag=true' >> $GITHUB_OUTPUT 47 | echo 'prerelease=false' >> $GITHUB_OUTPUT 48 | echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT 49 | ;; 50 | +([0-9]).+([0-9]).+([0-9])-@(beta|rc)*([0-9]) ) 51 | echo 'validTag=true' >> $GITHUB_OUTPUT 52 | echo 'prerelease=true' >> $GITHUB_OUTPUT 53 | echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT 54 | ;; 55 | *) echo 'validTag=false' >> $GITHUB_OUTPUT ;; 56 | esac 57 | 58 | - name: Download Build Artifacts 📥 59 | uses: actions/download-artifact@v4 60 | if: fromJSON(steps.check.outputs.validTag) 61 | id: download 62 | 63 | - name: Rename Files 🏷️ 64 | if: fromJSON(steps.check.outputs.validTag) 65 | run: | 66 | : Rename Files 🏷️ 67 | if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi 68 | shopt -s extglob 69 | shopt -s nullglob 70 | 71 | root_dir="$(pwd)" 72 | commit_hash="${GITHUB_SHA:0:9}" 73 | 74 | variants=( 75 | 'windows-x64;zip|exe' 76 | 'macos-universal;tar.xz|pkg' 77 | 'ubuntu-24.04-x86_64;tar.xz|deb|ddeb' 78 | 'sources;tar.xz' 79 | ) 80 | 81 | for variant_data in "${variants[@]}"; do 82 | IFS=';' read -r variant suffix <<< "${variant_data}" 83 | 84 | candidates=(*-${variant}-${commit_hash}/@(*|*-dbgsym).@(${suffix})) 85 | 86 | for candidate in "${candidates[@]}"; do 87 | mv "${candidate}" "${root_dir}" 88 | done 89 | done 90 | 91 | - name: Generate Checksums 🪪 92 | if: fromJSON(steps.check.outputs.validTag) 93 | run: | 94 | : Generate Checksums 🪪 95 | if [[ "${RUNNER_DEBUG}" ]]; then set -x; fi 96 | shopt -s extglob 97 | 98 | echo "### Checksums" > ${{ github.workspace }}/CHECKSUMS.txt 99 | for file in ${{ github.workspace }}/@(*.exe|*.deb|*.ddeb|*.pkg|*.tar.xz|*.zip); do 100 | echo " ${file##*/}: $(sha256sum "${file}" | cut -d " " -f 1)" >> ${{ github.workspace }}/CHECKSUMS.txt 101 | done 102 | 103 | - name: Create Release 🛫 104 | if: fromJSON(steps.check.outputs.validTag) 105 | id: create_release 106 | uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 107 | with: 108 | draft: true 109 | prerelease: ${{ fromJSON(steps.check.outputs.prerelease) }} 110 | tag_name: ${{ steps.check.outputs.version }} 111 | name: ${{ needs.build-project.outputs.pluginName }} ${{ steps.check.outputs.version }} 112 | body_path: ${{ github.workspace }}/CHECKSUMS.txt 113 | files: | 114 | ${{ github.workspace }}/*.exe 115 | ${{ github.workspace }}/*.zip 116 | ${{ github.workspace }}/*.pkg 117 | ${{ github.workspace }}/*.deb 118 | ${{ github.workspace }}/*.ddeb 119 | ${{ github.workspace }}/*.tar.xz 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # obs-plugin-lowerthirdswitcher 2 | An OBS Plugin to easily display pre-configured lower thirds. 3 | 4 | **Version 2** - Updated to use the latest OBS Plugin Template (2025) with support for OBS Studio 31 and Qt6. 5 | 6 | Screenshot 2024-01-23 at 14 07 45 7 | 8 |

9 | Say thanks with a chocolate bar :)
10 | 11 | PayPal Button 12 | 13 | 14 | 15 | ## Description 16 | Create an OBS folder that includes a text source for the main text (e.g. names) and one for the secondary subtitle (e.g. titles). The folder can also include any other design elements. 17 | 18 | Sources Screenshot 19 | 20 | In the OBS Docks menu, the "Lower Third Switcher" dock can be enabled. In the settings tab the scene, folder source, main text source and secondary source can be selected. The "Display Time" defines how long the lower third will be visible. If the "Display Time" is set to 0, the lower third gets triggered and becomes visible, but never hides. 21 | 22 | Settings Screenshot 23 | 24 | After the settings are set up, you can switch to the data tab in the Lower Third Switcher dock. For each lower third, you would like to show throughout the stream you can create a new entry. The item that has a green dot is the active item, which will be shown next. 25 | 26 | Data Screenshot 27 | 28 | During the stream/recording, you can show the next (active) item by clicking the "Play Next Lower Third" button, this action will also set the next item in line as active. A flashing red dot will indicate that the lower third is currently visible. 29 | A hotkey can be set up to show the next lower third. This enables many opportunities to control the plugin, e.g. a midi device can be connected and with a plugin like [obs-midi-mg](https://github.com/nhielost/obs-midi-mg/) midi signals can trigger the hotkey, thus the midi device can trigger the next lower third to be shown. 30 | 31 | ## Installing 32 | Go to the [Releases page](https://github.com/levi-hrb/obs-plugin-lowerthirdswitcher/releases) and download and install the latest release for the proper operating system. After installing the plugin, you will find it in OBS in the "Docks" menu. 33 | 34 | ## Building from Source 35 | 36 | ### Prerequisites 37 | - CMake 3.28 or newer 38 | - Xcode (macOS) or Visual Studio (Windows) 39 | - OBS Studio 31.0 or newer (automatically downloaded during build) 40 | - Qt6 (automatically downloaded during build) 41 | 42 | ### macOS Build Instructions 43 | ```bash 44 | # Clone the repository 45 | git clone https://github.com/anduloo/obs-plugin-lowerthirdswitcher.git 46 | cd obs-plugin-lowerthirdswitcher 47 | 48 | # Configure the project 49 | cmake -S . -B build -G Xcode -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" 50 | 51 | # Build the plugin 52 | cmake --build build --config Release 53 | 54 | # The plugin will be located at: 55 | # build/Release/obs-plugin-lowerthirdswitcher.plugin 56 | ``` 57 | 58 | ### Installing the Built Plugin 59 | Copy the built plugin to your OBS plugins directory: 60 | ```bash 61 | # User-specific installation 62 | cp -r build/Release/obs-plugin-lowerthirdswitcher.plugin ~/Library/Application\ Support/obs-studio/plugins/ 63 | 64 | # System-wide installation (requires sudo) 65 | sudo cp -r build/Release/obs-plugin-lowerthirdswitcher.plugin /Library/Application\ Support/obs-studio/plugins/ 66 | ``` 67 | 68 | ## Development 69 | This plugin uses the [OBS Plugin Template](https://github.com/obsproject/obs-plugintemplate) as its foundation. The build system automatically downloads required dependencies including OBS Studio sources, Qt6, and obs-deps. 70 | 71 | ### Project Structure 72 | - `src/` - Source code files 73 | - `data/` - Plugin resources (icons, UI files) 74 | - `cmake/` - CMake build configuration 75 | - `buildspec.json` - Dependency specification 76 | -------------------------------------------------------------------------------- /.github/scripts/build-macos: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | builtin emulate -L zsh 4 | setopt EXTENDED_GLOB 5 | setopt PUSHD_SILENT 6 | setopt ERR_EXIT 7 | setopt ERR_RETURN 8 | setopt NO_UNSET 9 | setopt PIPE_FAIL 10 | setopt NO_AUTO_PUSHD 11 | setopt NO_PUSHD_IGNORE_DUPS 12 | setopt FUNCTION_ARGZERO 13 | 14 | ## Enable for script debugging 15 | # setopt WARN_CREATE_GLOBAL 16 | # setopt WARN_NESTED_VAR 17 | # setopt XTRACE 18 | 19 | if (( ! ${+CI} )) { 20 | print -u2 -PR "%F{1} ✖︎ ${ZSH_ARGZERO:t:r} requires CI environment.%f" 21 | exit 1 22 | } 23 | 24 | autoload -Uz is-at-least && if ! is-at-least 5.9; then 25 | print -u2 -PR "${CI:+::error::}%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue." 26 | exit 1 27 | fi 28 | 29 | TRAPZERR() { 30 | print -u2 -PR "::error::%F{1} ✖︎ script execution error%f" 31 | print -PR -e " 32 | Callstack: 33 | ${(j:\n :)funcfiletrace} 34 | " 35 | 36 | exit 2 37 | } 38 | 39 | build() { 40 | if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h} 41 | local host_os='macos' 42 | local project_root=${SCRIPT_HOME:A:h:h} 43 | local buildspec_file=${project_root}/buildspec.json 44 | 45 | fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath}) 46 | autoload -Uz log_group log_info log_error log_output check_macos setup_ccache 47 | 48 | if [[ ! -r ${buildspec_file} ]] { 49 | log_error \ 50 | 'No buildspec.json found. Please create a build specification for your project.' 51 | return 2 52 | } 53 | 54 | local -i debug=0 55 | 56 | local config='RelWithDebInfo' 57 | local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel) 58 | local -i codesign=0 59 | 60 | local -a args 61 | while (( # )) { 62 | case ${1} { 63 | -c|--config) 64 | if (( # == 1 )) || [[ ${2:0:1} == '-' ]] { 65 | log_error "Missing value for option %B${1}%b" 66 | log_output ${_usage} 67 | exit 2 68 | } 69 | ;; 70 | } 71 | case ${1} { 72 | --) shift; args+=($@); break ;; 73 | -c|--config) 74 | if (( ! ${_valid_configs[(Ie)${2}]} )) { 75 | log_error "Invalid value %B${2}%b for option %B${1}%b" 76 | exit 2 77 | } 78 | config=${2} 79 | shift 2 80 | ;; 81 | -s|--codesign) codesign=1; shift ;; 82 | --debug) debug=1; shift ;; 83 | *) log_error "Unknown option: %B${1}%b"; exit 2 ;; 84 | } 85 | } 86 | 87 | set -- ${(@)args} 88 | 89 | check_macos 90 | 91 | local product_name 92 | local product_version 93 | read -r product_name product_version <<< \ 94 | "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})" 95 | 96 | pushd ${project_root} 97 | 98 | local -a cmake_args=() 99 | local -a cmake_build_args=(--build) 100 | local -a cmake_install_args=(--install) 101 | 102 | if (( debug )) cmake_args+=(--debug-output) 103 | 104 | cmake_args+=(--preset 'macos-ci') 105 | 106 | typeset -gx NSUnbufferedIO=YES 107 | 108 | typeset -gx CODESIGN_IDENT="${CODESIGN_IDENT:--}" 109 | if (( codesign )) && [[ -z ${CODESIGN_TEAM} ]] { 110 | typeset -gx CODESIGN_TEAM="$(print "${CODESIGN_IDENT}" | /usr/bin/sed -En 's/.+\((.+)\)/\1/p')" 111 | } 112 | 113 | log_group "Configuring ${product_name}..." 114 | cmake -S ${project_root} ${cmake_args} 115 | 116 | log_group "Building ${product_name}..." 117 | run_xcodebuild() { 118 | if (( debug )) { 119 | xcodebuild ${@} 120 | } else { 121 | if [[ ${GITHUB_EVENT_NAME} == push ]] { 122 | xcodebuild ${@} 2>&1 | xcbeautify --renderer terminal 123 | } else { 124 | xcodebuild ${@} 2>&1 | xcbeautify --renderer github-actions 125 | } 126 | } 127 | } 128 | 129 | local -a build_args=( 130 | ONLY_ACTIVE_ARCH=NO 131 | -arch arm64 132 | -arch x86_64 133 | -project ${product_name}.xcodeproj 134 | -target ${product_name} 135 | -destination "generic/platform=macOS,name=Any Mac" 136 | -configuration ${config} 137 | -parallelizeTargets 138 | -hideShellScriptEnvironment 139 | build 140 | ) 141 | 142 | pushd build_macos 143 | run_xcodebuild ${build_args} 144 | popd 145 | 146 | log_group "Installing ${product_name}..." 147 | cmake --install build_macos --config ${config} --prefix "${project_root}/release/${config}" 148 | 149 | popd 150 | log_group 151 | } 152 | 153 | build ${@} 154 | -------------------------------------------------------------------------------- /cmake/linux/helpers.cmake: -------------------------------------------------------------------------------- 1 | # CMake Linux helper functions module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(helpers_common) 6 | 7 | # set_target_properties_plugin: Set target properties for use in obs-studio 8 | function(set_target_properties_plugin target) 9 | set(options "") 10 | set(oneValueArgs "") 11 | set(multiValueArgs PROPERTIES) 12 | cmake_parse_arguments( 13 | PARSE_ARGV 14 | 0 15 | _STPO 16 | "${options}" 17 | "${oneValueArgs}" 18 | "${multiValueArgs}" 19 | ) 20 | 21 | message(DEBUG "Setting additional properties for target ${target}...") 22 | 23 | while(_STPO_PROPERTIES) 24 | list(POP_FRONT _STPO_PROPERTIES key value) 25 | set_property(TARGET ${target} PROPERTY ${key} "${value}") 26 | endwhile() 27 | 28 | set_target_properties( 29 | ${target} 30 | PROPERTIES 31 | VERSION ${PLUGIN_VERSION} 32 | SOVERSION ${PLUGIN_VERSION_MAJOR} 33 | PREFIX "" 34 | ) 35 | 36 | install( 37 | TARGETS ${target} 38 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 39 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/obs-plugins 40 | ) 41 | 42 | if(TARGET plugin-support) 43 | target_link_libraries(${target} PRIVATE plugin-support) 44 | endif() 45 | 46 | add_custom_command( 47 | TARGET ${target} 48 | POST_BUILD 49 | COMMAND 50 | "${CMAKE_COMMAND}" -E make_directory 51 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$" 52 | COMMAND 53 | "${CMAKE_COMMAND}" -E copy_if_different "$" 54 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$" 55 | COMMENT "Copy ${target} to rundir" 56 | VERBATIM 57 | ) 58 | 59 | target_install_resources(${target}) 60 | 61 | get_target_property(target_sources ${target} SOURCES) 62 | set(target_ui_files ${target_sources}) 63 | list(FILTER target_ui_files INCLUDE REGEX ".+\\.(ui|qrc)") 64 | source_group( 65 | TREE "${CMAKE_CURRENT_SOURCE_DIR}" 66 | PREFIX "UI Files" 67 | FILES ${target_ui_files} 68 | ) 69 | endfunction() 70 | 71 | # Helper function to add resources into bundle 72 | function(target_install_resources target) 73 | message(DEBUG "Installing resources for target ${target}...") 74 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") 75 | file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*") 76 | foreach(data_file IN LISTS data_files) 77 | cmake_path( 78 | RELATIVE_PATH 79 | data_file 80 | BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" 81 | OUTPUT_VARIABLE relative_path 82 | ) 83 | cmake_path(GET relative_path PARENT_PATH relative_path) 84 | target_sources(${target} PRIVATE "${data_file}") 85 | source_group("Resources/${relative_path}" FILES "${data_file}") 86 | endforeach() 87 | 88 | install( 89 | DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" 90 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-plugins/${target} 91 | USE_SOURCE_PERMISSIONS 92 | ) 93 | 94 | add_custom_command( 95 | TARGET ${target} 96 | POST_BUILD 97 | COMMAND 98 | "${CMAKE_COMMAND}" -E make_directory 99 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 100 | COMMAND 101 | "${CMAKE_COMMAND}" -E copy_directory 102 | "${CMAKE_CURRENT_SOURCE_DIR}/data" 103 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 104 | COMMENT "Copy ${target} resources to rundir" 105 | VERBATIM 106 | ) 107 | endif() 108 | endfunction() 109 | 110 | # Helper function to add a specific resource to a bundle 111 | function(target_add_resource target resource) 112 | message( 113 | DEBUG 114 | "Add resource '${resource}' to target ${target} at destination '${target_destination}'..." 115 | ) 116 | 117 | install( 118 | FILES "${resource}" 119 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-plugins/${target} 120 | ) 121 | 122 | add_custom_command( 123 | TARGET ${target} 124 | POST_BUILD 125 | COMMAND 126 | "${CMAKE_COMMAND}" -E make_directory 127 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 128 | COMMAND 129 | "${CMAKE_COMMAND}" -E copy "${resource}" 130 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 131 | COMMENT "Copy ${target} resource ${resource} to rundir" 132 | VERBATIM 133 | ) 134 | 135 | source_group("Resources" FILES "${resource}") 136 | endfunction() 137 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 8, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 28, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "template", 11 | "hidden": true, 12 | "cacheVariables": { 13 | "ENABLE_FRONTEND_API": true, 14 | "ENABLE_QT": true 15 | } 16 | }, 17 | { 18 | "name": "macos", 19 | "displayName": "macOS Universal", 20 | "description": "Build for macOS 12.0+ (Universal binary)", 21 | "inherits": ["template"], 22 | "binaryDir": "${sourceDir}/build_macos", 23 | "condition": { 24 | "type": "equals", 25 | "lhs": "${hostSystemName}", 26 | "rhs": "Darwin" 27 | }, 28 | "generator": "Xcode", 29 | "warnings": {"dev": true, "deprecated": true}, 30 | "cacheVariables": { 31 | "CMAKE_OSX_DEPLOYMENT_TARGET": "12.0", 32 | "CMAKE_OSX_ARCHITECTURES": "arm64;x86_64", 33 | "CODESIGN_IDENTITY": "$penv{CODESIGN_IDENT}", 34 | "CODESIGN_TEAM": "$penv{CODESIGN_TEAM}" 35 | } 36 | }, 37 | { 38 | "name": "macos-ci", 39 | "inherits": ["macos"], 40 | "displayName": "macOS Universal CI build", 41 | "description": "Build for macOS 12.0+ (Universal binary) for CI", 42 | "generator": "Xcode", 43 | "cacheVariables": { 44 | "CMAKE_COMPILE_WARNING_AS_ERROR": true, 45 | "ENABLE_CCACHE": true 46 | } 47 | }, 48 | { 49 | "name": "windows-x64", 50 | "displayName": "Windows x64", 51 | "description": "Build for Windows x64", 52 | "inherits": ["template"], 53 | "binaryDir": "${sourceDir}/build_x64", 54 | "condition": { 55 | "type": "equals", 56 | "lhs": "${hostSystemName}", 57 | "rhs": "Windows" 58 | }, 59 | "generator": "Visual Studio 17 2022", 60 | "architecture": "x64,version=10.0.22621", 61 | "warnings": {"dev": true, "deprecated": true} 62 | }, 63 | { 64 | "name": "windows-ci-x64", 65 | "inherits": ["windows-x64"], 66 | "displayName": "Windows x64 CI build", 67 | "description": "Build for Windows x64 on CI", 68 | "cacheVariables": { 69 | "CMAKE_COMPILE_WARNING_AS_ERROR": true 70 | } 71 | }, 72 | { 73 | "name": "ubuntu-x86_64", 74 | "displayName": "Ubuntu x86_64", 75 | "description": "Build for Ubuntu x86_64", 76 | "inherits": ["template"], 77 | "binaryDir": "${sourceDir}/build_x86_64", 78 | "condition": { 79 | "type": "equals", 80 | "lhs": "${hostSystemName}", 81 | "rhs": "Linux" 82 | }, 83 | "generator": "Ninja", 84 | "warnings": {"dev": true, "deprecated": true}, 85 | "cacheVariables": { 86 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 87 | "CMAKE_INSTALL_LIBDIR": "lib/CMAKE_SYSTEM_PROCESSOR-linux-gnu" 88 | } 89 | }, 90 | { 91 | "name": "ubuntu-ci-x86_64", 92 | "inherits": ["ubuntu-x86_64"], 93 | "displayName": "Ubuntu x86_64 CI build", 94 | "description": "Build for Ubuntu x86_64 on CI", 95 | "cacheVariables": { 96 | "CMAKE_BUILD_TYPE": "RelWithDebInfo", 97 | "CMAKE_COMPILE_WARNING_AS_ERROR": true, 98 | "ENABLE_CCACHE": true 99 | } 100 | } 101 | ], 102 | "buildPresets": [ 103 | { 104 | "name": "macos", 105 | "configurePreset": "macos", 106 | "displayName": "macOS Universal", 107 | "description": "macOS build for Universal architectures", 108 | "configuration": "RelWithDebInfo" 109 | }, 110 | { 111 | "name": "macos-ci", 112 | "configurePreset": "macos-ci", 113 | "displayName": "macOS Universal CI", 114 | "description": "macOS CI build for Universal architectures", 115 | "configuration": "RelWithDebInfo" 116 | }, 117 | { 118 | "name": "windows-x64", 119 | "configurePreset": "windows-x64", 120 | "displayName": "Windows x64", 121 | "description": "Windows build for x64", 122 | "configuration": "RelWithDebInfo" 123 | }, 124 | { 125 | "name": "windows-ci-x64", 126 | "configurePreset": "windows-ci-x64", 127 | "displayName": "Windows x64 CI", 128 | "description": "Windows CI build for x64 (RelWithDebInfo configuration)", 129 | "configuration": "RelWithDebInfo" 130 | }, 131 | { 132 | "name": "ubuntu-x86_64", 133 | "configurePreset": "ubuntu-x86_64", 134 | "displayName": "Ubuntu x86_64", 135 | "description": "Ubuntu build for x86_64", 136 | "configuration": "RelWithDebInfo" 137 | }, 138 | { 139 | "name": "ubuntu-ci-x86_64", 140 | "configurePreset": "ubuntu-ci-x86_64", 141 | "displayName": "Ubuntu x86_64 CI", 142 | "description": "Ubuntu CI build for x86_64", 143 | "configuration": "RelWithDebInfo" 144 | } 145 | ] 146 | } -------------------------------------------------------------------------------- /cmake/windows/helpers.cmake: -------------------------------------------------------------------------------- 1 | # CMake Windows helper functions module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(helpers_common) 6 | 7 | # set_target_properties_plugin: Set target properties for use in obs-studio 8 | function(set_target_properties_plugin target) 9 | set(options "") 10 | set(oneValueArgs "") 11 | set(multiValueArgs PROPERTIES) 12 | cmake_parse_arguments( 13 | PARSE_ARGV 14 | 0 15 | _STPO 16 | "${options}" 17 | "${oneValueArgs}" 18 | "${multiValueArgs}" 19 | ) 20 | 21 | message(DEBUG "Setting additional properties for target ${target}...") 22 | 23 | while(_STPO_PROPERTIES) 24 | list(POP_FRONT _STPO_PROPERTIES key value) 25 | set_property(TARGET ${target} PROPERTY ${key} "${value}") 26 | endwhile() 27 | 28 | string(TIMESTAMP CURRENT_YEAR "%Y") 29 | 30 | set_target_properties( 31 | ${target} 32 | PROPERTIES VERSION 0 SOVERSION ${PLUGIN_VERSION} 33 | ) 34 | 35 | install( 36 | TARGETS ${target} 37 | RUNTIME DESTINATION "${target}/bin/64bit" 38 | LIBRARY DESTINATION "${target}/bin/64bit" 39 | ) 40 | 41 | install( 42 | FILES "$" 43 | CONFIGURATIONS RelWithDebInfo Debug Release 44 | DESTINATION "${target}/bin/64bit" 45 | OPTIONAL 46 | ) 47 | 48 | if(TARGET plugin-support) 49 | target_link_libraries(${target} PRIVATE plugin-support) 50 | endif() 51 | 52 | add_custom_command( 53 | TARGET ${target} 54 | POST_BUILD 55 | COMMAND 56 | "${CMAKE_COMMAND}" -E make_directory 57 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$" 58 | COMMAND 59 | "${CMAKE_COMMAND}" -E copy_if_different "$" 60 | "$<$:$>" 61 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$" 62 | COMMENT "Copy ${target} to rundir" 63 | VERBATIM 64 | ) 65 | 66 | target_install_resources(${target}) 67 | 68 | get_target_property(target_sources ${target} SOURCES) 69 | set(target_ui_files ${target_sources}) 70 | list(FILTER target_ui_files INCLUDE REGEX ".+\\.(ui|qrc)") 71 | source_group( 72 | TREE "${CMAKE_CURRENT_SOURCE_DIR}" 73 | PREFIX "UI Files" 74 | FILES ${target_ui_files} 75 | ) 76 | 77 | configure_file( 78 | cmake/windows/resources/resource.rc.in 79 | "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.rc" 80 | ) 81 | target_sources( 82 | ${CMAKE_PROJECT_NAME} 83 | PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.rc" 84 | ) 85 | endfunction() 86 | 87 | # Helper function to add resources into bundle 88 | function(target_install_resources target) 89 | message(DEBUG "Installing resources for target ${target}...") 90 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") 91 | file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*") 92 | foreach(data_file IN LISTS data_files) 93 | cmake_path( 94 | RELATIVE_PATH 95 | data_file 96 | BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" 97 | OUTPUT_VARIABLE relative_path 98 | ) 99 | cmake_path(GET relative_path PARENT_PATH relative_path) 100 | target_sources(${target} PRIVATE "${data_file}") 101 | source_group("Resources/${relative_path}" FILES "${data_file}") 102 | endforeach() 103 | 104 | install( 105 | DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" 106 | DESTINATION "${target}/data" 107 | USE_SOURCE_PERMISSIONS 108 | ) 109 | 110 | add_custom_command( 111 | TARGET ${target} 112 | POST_BUILD 113 | COMMAND 114 | "${CMAKE_COMMAND}" -E make_directory 115 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 116 | COMMAND 117 | "${CMAKE_COMMAND}" -E copy_directory 118 | "${CMAKE_CURRENT_SOURCE_DIR}/data" 119 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 120 | COMMENT "Copy ${target} resources to rundir" 121 | VERBATIM 122 | ) 123 | endif() 124 | endfunction() 125 | 126 | # Helper function to add a specific resource to a bundle 127 | function(target_add_resource target resource) 128 | message( 129 | DEBUG 130 | "Add resource '${resource}' to target ${target} at destination '${target_destination}'..." 131 | ) 132 | 133 | install(FILES "${resource}" DESTINATION "${target}/data" COMPONENT Runtime) 134 | 135 | add_custom_command( 136 | TARGET ${target} 137 | POST_BUILD 138 | COMMAND 139 | "${CMAKE_COMMAND}" -E make_directory 140 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 141 | COMMAND 142 | "${CMAKE_COMMAND}" -E copy "${resource}" 143 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/${target}" 144 | COMMENT "Copy ${target} resource ${resource} to rundir" 145 | VERBATIM 146 | ) 147 | source_group("Resources" FILES "${resource}") 148 | endfunction() 149 | -------------------------------------------------------------------------------- /cmake/macos/helpers.cmake: -------------------------------------------------------------------------------- 1 | # CMake macOS helper functions module 2 | 3 | include_guard(GLOBAL) 4 | 5 | include(helpers_common) 6 | 7 | # set_target_properties_obs: Set target properties for use in obs-studio 8 | function(set_target_properties_plugin target) 9 | set(options "") 10 | set(oneValueArgs "") 11 | set(multiValueArgs PROPERTIES) 12 | cmake_parse_arguments( 13 | PARSE_ARGV 14 | 0 15 | _STPO 16 | "${options}" 17 | "${oneValueArgs}" 18 | "${multiValueArgs}" 19 | ) 20 | 21 | message(DEBUG "Setting additional properties for target ${target}...") 22 | 23 | while(_STPO_PROPERTIES) 24 | list(POP_FRONT _STPO_PROPERTIES key value) 25 | set_property(TARGET ${target} PROPERTY ${key} "${value}") 26 | endwhile() 27 | 28 | string(TIMESTAMP CURRENT_YEAR "%Y") 29 | set_target_properties( 30 | ${target} 31 | PROPERTIES 32 | BUNDLE TRUE 33 | BUNDLE_EXTENSION plugin 34 | XCODE_ATTRIBUTE_PRODUCT_NAME ${target} 35 | XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${MACOS_BUNDLEID} 36 | XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION ${PLUGIN_BUILD_NUMBER} 37 | XCODE_ATTRIBUTE_MARKETING_VERSION ${PLUGIN_VERSION} 38 | XCODE_ATTRIBUTE_GENERATE_INFOPLIST_FILE YES 39 | XCODE_ATTRIBUTE_INFOPLIST_FILE "" 40 | XCODE_ATTRIBUTE_INFOPLIST_KEY_CFBundleDisplayName ${target} 41 | XCODE_ATTRIBUTE_INFOPLIST_KEY_NSHumanReadableCopyright 42 | "(c) ${CURRENT_YEAR} ${PLUGIN_AUTHOR}" 43 | XCODE_ATTRIBUTE_INSTALL_PATH 44 | "$(USER_LIBRARY_DIR)/Application Support/obs-studio/plugins" 45 | ) 46 | 47 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist") 48 | set_target_properties( 49 | ${target} 50 | PROPERTIES 51 | XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS 52 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist" 53 | ) 54 | endif() 55 | 56 | if(TARGET plugin-support) 57 | target_link_libraries(${target} PRIVATE plugin-support) 58 | endif() 59 | 60 | target_install_resources(${target}) 61 | 62 | add_custom_command( 63 | TARGET ${target} 64 | POST_BUILD 65 | COMMAND 66 | "${CMAKE_COMMAND}" -E make_directory 67 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$" 68 | COMMAND 69 | "${CMAKE_COMMAND}" -E copy_directory 70 | "$" 71 | "${CMAKE_CURRENT_BINARY_DIR}/rundir/$/$" 72 | COMMENT "Copy ${target} to rundir" 73 | VERBATIM 74 | ) 75 | 76 | get_target_property(target_sources ${target} SOURCES) 77 | set(target_ui_files ${target_sources}) 78 | list(FILTER target_ui_files INCLUDE REGEX ".+\\.(ui|qrc)") 79 | source_group( 80 | TREE "${CMAKE_CURRENT_SOURCE_DIR}" 81 | PREFIX "UI Files" 82 | FILES ${target_ui_files} 83 | ) 84 | 85 | install(TARGETS ${target} LIBRARY DESTINATION .) 86 | install( 87 | FILES "$.dsym" 88 | CONFIGURATIONS Release 89 | DESTINATION . 90 | OPTIONAL 91 | ) 92 | 93 | configure_file( 94 | cmake/macos/resources/distribution.in 95 | "${CMAKE_CURRENT_BINARY_DIR}/distribution" 96 | @ONLY 97 | ) 98 | configure_file( 99 | cmake/macos/resources/create-package.cmake.in 100 | "${CMAKE_CURRENT_BINARY_DIR}/create-package.cmake" 101 | @ONLY 102 | ) 103 | install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/create-package.cmake") 104 | endfunction() 105 | 106 | # target_install_resources: Helper function to add resources into bundle 107 | function(target_install_resources target) 108 | message(DEBUG "Installing resources for target ${target}...") 109 | if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") 110 | file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*") 111 | foreach(data_file IN LISTS data_files) 112 | cmake_path( 113 | RELATIVE_PATH 114 | data_file 115 | BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" 116 | OUTPUT_VARIABLE relative_path 117 | ) 118 | cmake_path(GET relative_path PARENT_PATH relative_path) 119 | target_sources(${target} PRIVATE "${data_file}") 120 | set_property( 121 | SOURCE "${data_file}" 122 | PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${relative_path}" 123 | ) 124 | source_group("Resources/${relative_path}" FILES "${data_file}") 125 | endforeach() 126 | endif() 127 | endfunction() 128 | 129 | # target_add_resource: Helper function to add a specific resource to a bundle 130 | function(target_add_resource target resource) 131 | message( 132 | DEBUG 133 | "Add resource ${resource} to target ${target} at destination ${destination}..." 134 | ) 135 | target_sources(${target} PRIVATE "${resource}") 136 | set_property( 137 | SOURCE "${resource}" 138 | PROPERTY MACOSX_PACKAGE_LOCATION Resources 139 | ) 140 | source_group("Resources" FILES "${resource}") 141 | endfunction() 142 | -------------------------------------------------------------------------------- /.github/scripts/package-macos: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | builtin emulate -L zsh 4 | setopt EXTENDED_GLOB 5 | setopt PUSHD_SILENT 6 | setopt ERR_EXIT 7 | setopt ERR_RETURN 8 | setopt NO_UNSET 9 | setopt PIPE_FAIL 10 | setopt NO_AUTO_PUSHD 11 | setopt NO_PUSHD_IGNORE_DUPS 12 | setopt FUNCTION_ARGZERO 13 | 14 | ## Enable for script debugging 15 | # setopt WARN_CREATE_GLOBAL 16 | # setopt WARN_NESTED_VAR 17 | # setopt XTRACE 18 | 19 | if (( ! ${+CI} )) { 20 | print -u2 -PR "%F{1} ✖︎ ${ZSH_ARGZERO:t:r} requires CI environment%f" 21 | exit 1 22 | } 23 | 24 | autoload -Uz is-at-least && if ! is-at-least 5.9; then 25 | print -u2 -PR "${CI:+::error::}%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue." 26 | exit 1 27 | fi 28 | 29 | TRAPZERR() { 30 | print -u2 -PR "::error::%F{1} ✖︎ script execution error%f" 31 | print -PR -e " 32 | Callstack: 33 | ${(j:\n :)funcfiletrace} 34 | " 35 | 36 | exit 2 37 | } 38 | 39 | package() { 40 | if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h} 41 | local host_os='macos' 42 | local project_root=${SCRIPT_HOME:A:h:h} 43 | local buildspec_file=${project_root}/buildspec.json 44 | 45 | fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath}) 46 | autoload -Uz log_group log_error log_output check_macos 47 | 48 | if [[ ! -r ${buildspec_file} ]] { 49 | log_error \ 50 | 'No buildspec.json found. Please create a build specification for your project.' 51 | return 2 52 | } 53 | 54 | local -i debug=0 55 | 56 | local config='RelWithDebInfo' 57 | local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel) 58 | 59 | local -i codesign=0 60 | local -i notarize=0 61 | local -i package=0 62 | 63 | local -a args 64 | while (( # )) { 65 | case ${1} { 66 | -c|--config) 67 | if (( # == 1 )) || [[ ${2:0:1} == '-' ]] { 68 | log_error "Missing value for option %B${1}%b" 69 | exit 2 70 | } 71 | ;; 72 | } 73 | case ${1} { 74 | --) shift; args+=($@); break ;; 75 | -c|--config) 76 | if (( !${_valid_configs[(Ie)${2}]} )) { 77 | log_error "Invalid value %B${2}%b for option %B${1}%b" 78 | exit 2 79 | } 80 | config=${2} 81 | shift 2 82 | ;; 83 | -s|--codesign) typeset -g codesign=1; shift ;; 84 | -n|--notarize) typeset -g notarize=1; typeset -g codesign=1; shift ;; 85 | -p|--package) typeset -g package=1; shift ;; 86 | --debug) debug=1; shift ;; 87 | *) log_error "Unknown option: %B${1}%b"; exit 2 ;; 88 | } 89 | } 90 | 91 | set -- ${(@)args} 92 | 93 | check_macos 94 | 95 | local product_name 96 | local product_version 97 | read -r product_name product_version <<< \ 98 | "$(jq -r '. | {name, version} | join(" ")' ${buildspec_file})" 99 | 100 | local output_name="${product_name}-${product_version}-${host_os}-universal" 101 | 102 | if [[ ! -d ${project_root}/release/${config}/${product_name}.plugin ]] { 103 | log_error 'No release artifact found. Run the build script or the CMake install procedure first.' 104 | return 2 105 | } 106 | 107 | if (( package )) { 108 | if [[ ! -f ${project_root}/release/${config}/${product_name}.pkg ]] { 109 | log_error 'Installer Package not found. Run the build script or the CMake build and install procedures first.' 110 | return 2 111 | } 112 | 113 | log_group "Packaging ${product_name}..." 114 | pushd ${project_root} 115 | 116 | typeset -gx CODESIGN_IDENT="${CODESIGN_IDENT:--}" 117 | typeset -gx CODESIGN_IDENT_INSTALLER="${CODESIGN_IDENT_INSTALLER:--}" 118 | typeset -gx CODESIGN_TEAM="$(print "${CODESIGN_IDENT}" | /usr/bin/sed -En 's/.+\((.+)\)/\1/p')" 119 | 120 | if (( codesign )) { 121 | productsign \ 122 | --sign "${CODESIGN_IDENT_INSTALLER}" \ 123 | ${project_root}/release/${config}/${product_name}.pkg \ 124 | ${project_root}/release/${output_name}.pkg 125 | 126 | rm ${project_root}/release/${config}/${product_name}.pkg 127 | } else { 128 | mv ${project_root}/release/${config}/${product_name}.pkg \ 129 | ${project_root}/release/${output_name}.pkg 130 | } 131 | 132 | if (( codesign && notarize )) { 133 | if ! [[ ${CODESIGN_IDENT} != '-' && ${CODESIGN_TEAM} && {CODESIGN_IDENT_USER} && ${CODESIGN_IDENT_PASS} ]] { 134 | log_error "Notarization requires Apple ID and application password." 135 | return 2 136 | } 137 | 138 | if [[ ! -f ${project_root}/release/${output_name}.pkg ]] { 139 | log_error "No package for notarization found." 140 | return 2 141 | } 142 | 143 | xcrun notarytool store-credentials "${product_name}-Codesign-Password" --apple-id "${CODESIGN_IDENT_USER}" --team-id "${CODESIGN_TEAM}" --password "${CODESIGN_IDENT_PASS}" 144 | xcrun notarytool submit ${project_root}/release/${output_name}.pkg --keychain-profile "${product_name}-Codesign-Password" --wait 145 | 146 | local -i _status=0 147 | 148 | xcrun stapler staple ${project_root}/release/${output_name}.pkg || _status=1 149 | 150 | if (( _status )) { 151 | log_error "Notarization failed. Use 'xcrun notarytool log ' to check for errors." 152 | return 2 153 | } 154 | } 155 | popd 156 | } else { 157 | log_group "Archiving ${product_name}..." 158 | pushd ${project_root}/release/${config} 159 | XZ_OPT=-T0 tar -cvJf ${project_root}/release/${output_name}.tar.xz ${product_name}.plugin 160 | popd 161 | } 162 | 163 | if [[ ${config} == Release ]] { 164 | log_group "Archiving ${product_name} Debug Symbols..." 165 | pushd ${project_root}/release/${config} 166 | XZ_OPT=-T0 tar -cvJf ${project_root}/release/${output_name}-dSYMs.tar.xz ${product_name}.plugin.dSYM 167 | popd 168 | } 169 | 170 | log_group 171 | } 172 | 173 | package ${@} 174 | -------------------------------------------------------------------------------- /.github/scripts/package-ubuntu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | 3 | builtin emulate -L zsh 4 | setopt EXTENDED_GLOB 5 | setopt PUSHD_SILENT 6 | setopt ERR_EXIT 7 | setopt ERR_RETURN 8 | setopt NO_UNSET 9 | setopt PIPE_FAIL 10 | setopt NO_AUTO_PUSHD 11 | setopt NO_PUSHD_IGNORE_DUPS 12 | setopt FUNCTION_ARGZERO 13 | 14 | ## Enable for script debugging 15 | # setopt WARN_CREATE_GLOBAL 16 | # setopt WARN_NESTED_VAR 17 | # setopt XTRACE 18 | 19 | autoload -Uz is-at-least && if ! is-at-least 5.2; then 20 | print -u2 -PR "${CI:+::error::}%F{1}${funcstack[1]##*/}:%f Running on Zsh version %B${ZSH_VERSION}%b, but Zsh %B5.2%b is the minimum supported version. Upgrade Zsh to fix this issue." 21 | exit 1 22 | fi 23 | 24 | TRAPZERR() { 25 | if (( ${_loglevel:-3} > 2 )) { 26 | print -u2 -PR "${CI:+::error::}%F{1} ✖︎ script execution error%f" 27 | print -PR -e " 28 | Callstack: 29 | ${(j:\n :)funcfiletrace} 30 | " 31 | } 32 | 33 | exit 2 34 | } 35 | 36 | package() { 37 | if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h} 38 | local host_os='ubuntu' 39 | local project_root=${SCRIPT_HOME:A:h:h} 40 | local buildspec_file=${project_root}/buildspec.json 41 | 42 | fpath=("${SCRIPT_HOME}/utils.zsh" ${fpath}) 43 | autoload -Uz set_loglevel log_info log_group log_error log_output check_${host_os} 44 | 45 | if [[ ! -r ${buildspec_file} ]] { 46 | log_error \ 47 | 'No buildspec.json found. Please create a build specification for your project.' 48 | return 2 49 | } 50 | 51 | local -i debug=0 52 | local -i verbosity=1 53 | local -r _version='2.2.1' 54 | local -r -a _valid_targets=( 55 | ubuntu-x86_64 56 | ) 57 | local target 58 | local config='RelWithDebInfo' 59 | local -r -a _valid_configs=(Debug RelWithDebInfo Release MinSizeRel) 60 | local -i codesign=0 61 | local -i notarize=0 62 | local -i package=0 63 | local -i skip_deps=0 64 | 65 | local -r _usage=" 66 | Usage: %B${functrace[1]%:*}%b