├── 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 |
7 |
8 |
9 | Say thanks with a chocolate bar :)
10 |
11 |
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 |
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 |
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 |
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