Initial import from Aitum/obs-vertical-canvas v1.6.4
Forked for Vulcast branding changes. Licensed under GPL-2.0.
17
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
*~
|
||||
.DS_Store
|
||||
/build/
|
||||
/build_*/
|
||||
/release/
|
||||
/installer/Output/
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
.deps/
|
||||
|
||||
# ignore generated files
|
||||
*.generated.*
|
||||
**/.Brewfile.lock.json
|
||||
version.h
|
||||
cmake/.CMakeBuildNumber
|
||||
.ccache.conf
|
||||
97
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# --- Detect if the plugin is build out of tree or not ---
|
||||
if(CMAKE_PROJECT_NAME STREQUAL "obs-studio")
|
||||
set(BUILD_OUT_OF_TREE OFF)
|
||||
else()
|
||||
set(BUILD_OUT_OF_TREE ON)
|
||||
cmake_minimum_required(VERSION 3.16...3.26)
|
||||
endif()
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/common/bootstrap.cmake" NO_POLICY_SCOPE)
|
||||
|
||||
project(${_name} VERSION ${_version})
|
||||
|
||||
include(compilerconfig)
|
||||
include(defaults)
|
||||
include(helpers)
|
||||
|
||||
add_library(${PROJECT_NAME} MODULE)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE OBS::libobs)
|
||||
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
find_package(libobs REQUIRED)
|
||||
find_package(obs-frontend-api REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE OBS::obs-frontend-api)
|
||||
else()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE OBS::frontend-api)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 COMPONENTS Widgets Core)
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
|
||||
find_package(Qt6 REQUIRED Gui)
|
||||
endif()
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt::Core Qt::Widgets)
|
||||
|
||||
if((OS_LINUX OR OS_FREEBSD OR OS_OPENBSD) AND Qt6_VERSION VERSION_LESS "6.9.0")
|
||||
find_package(Qt6 COMPONENTS GuiPrivate)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE Qt::GuiPrivate)
|
||||
endif()
|
||||
|
||||
target_compile_options(
|
||||
${PROJECT_NAME} PRIVATE $<$<C_COMPILER_ID:Clang,AppleClang>:-Wno-quoted-include-in-framework-header
|
||||
-Wno-comma>)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
set_target_properties(
|
||||
${PROJECT_NAME}
|
||||
PROPERTIES AUTOMOC ON
|
||||
AUTOUIC ON
|
||||
AUTORCC ON)
|
||||
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
find_package(CURL REQUIRED)
|
||||
endif()
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE CURL::libcurl)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/version.h)
|
||||
|
||||
if(OS_WINDOWS)
|
||||
configure_file(cmake/windows/resources/installer-Windows.iss.in "${CMAKE_CURRENT_BINARY_DIR}/installer-Windows.generated.iss")
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT_NAME} PRIVATE
|
||||
vertical-canvas.cpp
|
||||
scenes-dock.cpp
|
||||
sources-dock.cpp
|
||||
source-tree.cpp
|
||||
transitions-dock.cpp
|
||||
qt-display.cpp
|
||||
projector.cpp
|
||||
config-dialog.cpp
|
||||
hotkey-edit.cpp
|
||||
name-dialog.cpp
|
||||
audio-wrapper-source.c
|
||||
file-updater.c
|
||||
multi-canvas-source.c
|
||||
resources.qrc
|
||||
vertical-canvas.hpp
|
||||
scenes-dock.hpp
|
||||
sources-dock.hpp
|
||||
source-tree.hpp
|
||||
transitions-dock.hpp
|
||||
qt-display.hpp
|
||||
projector.hpp
|
||||
display-helpers.hpp
|
||||
config-dialog.hpp
|
||||
hotkey-edit.hpp
|
||||
name-dialog.hpp
|
||||
audio-wrapper-source.h
|
||||
obs-websocket-api.h
|
||||
file-updater.h
|
||||
multi-canvas-source.h)
|
||||
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name})
|
||||
else()
|
||||
set_target_properties_obs(${PROJECT_NAME} PROPERTIES FOLDER "plugins/aitum" PREFIX "")
|
||||
endif()
|
||||
190
CMakePresets.json
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
{
|
||||
"version": 3,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 22,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "template",
|
||||
"hidden": true,
|
||||
"cacheVariables": {
|
||||
"ENABLE_FRONTEND_API": false,
|
||||
"ENABLE_QT": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos",
|
||||
"displayName": "macOS Universal",
|
||||
"description": "Build for macOS (Universal binary)",
|
||||
"inherits": ["template"],
|
||||
"binaryDir": "${sourceDir}/build_macos",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
},
|
||||
"generator": "Xcode",
|
||||
"warnings": {"dev": true, "deprecated": true},
|
||||
"cacheVariables": {
|
||||
"QT_VERSION": "6",
|
||||
"CMAKE_OSX_DEPLOYMENT_TARGET": "12.0",
|
||||
"CODESIGN_IDENTITY": "$penv{CODESIGN_IDENT}",
|
||||
"CODESIGN_TEAM": "$penv{APPLE_TEAM}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos-ci",
|
||||
"inherits": ["macos"],
|
||||
"displayName": "macOS Universal CI build",
|
||||
"description": "Build for macOS (Universal binary) for CI",
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-x64",
|
||||
"displayName": "Windows x64",
|
||||
"description": "Build for Windows x64",
|
||||
"inherits": ["template"],
|
||||
"binaryDir": "${sourceDir}/build_x64",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"architecture": "x64",
|
||||
"warnings": {"dev": true, "deprecated": true},
|
||||
"cacheVariables": {
|
||||
"QT_VERSION": "6",
|
||||
"CMAKE_SYSTEM_VERSION": "10.0.18363.657"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-ci-x64",
|
||||
"inherits": ["windows-x64"],
|
||||
"displayName": "Windows x64 CI build",
|
||||
"description": "Build for Windows x64 on CI",
|
||||
"cacheVariables": {
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ubuntu-x86_64",
|
||||
"displayName": "Ubuntu x86_64",
|
||||
"description": "Build for Ubuntu x86_64",
|
||||
"inherits": ["template"],
|
||||
"binaryDir": "${sourceDir}/build_x86_64",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"generator": "Ninja",
|
||||
"warnings": {"dev": true, "deprecated": true},
|
||||
"cacheVariables": {
|
||||
"QT_VERSION": "6",
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ubuntu-ci-x86_64",
|
||||
"inherits": ["ubuntu-x86_64"],
|
||||
"displayName": "Ubuntu x86_64 CI build",
|
||||
"description": "Build for Ubuntu x86_64 on CI",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-aarch64",
|
||||
"displayName": "Linux aarch64",
|
||||
"description": "Build for Linux aarch64",
|
||||
"inherits": ["template"],
|
||||
"binaryDir": "${sourceDir}/build_aarch64",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"generator": "Ninja",
|
||||
"warnings": {"dev": true, "deprecated": true},
|
||||
"cacheVariables": {
|
||||
"QT_VERSION": "6",
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-ci-aarch64",
|
||||
"inherits": ["linux-aarch64"],
|
||||
"displayName": "Linux aarch64 CI build",
|
||||
"description": "Build for Linux aarch64 on CI",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||
"CMAKE_COMPILE_WARNING_AS_ERROR": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "macos",
|
||||
"configurePreset": "macos",
|
||||
"displayName": "macOS Universal",
|
||||
"description": "macOS build for Universal architectures",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "macos-ci",
|
||||
"configurePreset": "macos-ci",
|
||||
"displayName": "macOS Universal CI",
|
||||
"description": "macOS CI build for Universal architectures",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "windows-x64",
|
||||
"configurePreset": "windows-x64",
|
||||
"displayName": "Windows x64",
|
||||
"description": "Windows build for x64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "windows-ci-x64",
|
||||
"configurePreset": "windows-ci-x64",
|
||||
"displayName": "Windows x64 CI",
|
||||
"description": "Windows CI build for x64 (RelWithDebInfo configuration)",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "ubuntu-x86_64",
|
||||
"configurePreset": "ubuntu-x86_64",
|
||||
"displayName": "Linux x86_64",
|
||||
"description": "Linux build for x86_64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "ubuntu-ci-x86_64",
|
||||
"configurePreset": "ubuntu-ci-x86_64",
|
||||
"displayName": "Linux x86_64 CI",
|
||||
"description": "Linux CI build for x86_64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "linux-aarch64",
|
||||
"configurePreset": "linux-aarch64",
|
||||
"displayName": "Linux aarch64",
|
||||
"description": "Linux build for aarch64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "linux-ci-aarch64",
|
||||
"configurePreset": "linux-ci-aarch64",
|
||||
"displayName": "Linux aarch64 CI",
|
||||
"description": "Linux CI build for aarch64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
}
|
||||
]
|
||||
}
|
||||
339
LICENSE
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
28
README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Vertical Canvas for OBS Studio
|
||||
|
||||
Plugin for [OBS Studio](https://github.com/obsproject/obs-studio) to add vertical canvas by [ Aitum](https://aitum.tv)
|
||||
|
||||
# Support
|
||||
|
||||
Currently supported distributions can be found at:
|
||||
|
||||
* Aitum Website: https://aitum.tv/vertical
|
||||
* GitHub: https://github.com/Aitum/obs-vertical-canvas
|
||||
* Flatpak: https://github.com/flathub/com.obsproject.Studio.Plugin.VerticalCanvas
|
||||
|
||||
For support, please see our troubleshooter: https://l.aitum.tv/vh
|
||||
|
||||
If this does not solve your issue, please use the relevant channel on our [Discord server](https://aitum.tv/discord).
|
||||
|
||||
# Build
|
||||
- In-tree build
|
||||
- Build OBS Studio: https://obsproject.com/wiki/Install-Instructions
|
||||
- Check out this repository to UI/frontend-plugins/vertical-canvas
|
||||
- Add `add_subdirectory(vertical-canvas)` to UI/frontend-plugins/CMakeLists.txt
|
||||
- Rebuild OBS Studio
|
||||
- Stand-alone build
|
||||
- Verify that you have development files for OBS
|
||||
- Check out this repository and run `cmake -S . -B build -DBUILD_OUT_OF_TREE=On && cmake --build build`
|
||||
|
||||
# Translations
|
||||
Please read [Translations](TRANSLATIONS.md)
|
||||
8
TRANSLATIONS.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Translations
|
||||
|
||||
If you'd like to contribute a translation for the Aitum Vertical Plugin into your language, feel free to start a PR.
|
||||
|
||||
We ask that the following terms are not translated however, mostly as they are "branding" for us and thus should be consistent between languages.
|
||||
|
||||
- Aitum
|
||||
- Backtrack
|
||||
100
audio-wrapper-source.c
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include <obs.h>
|
||||
#include "audio-wrapper-source.h"
|
||||
|
||||
const char *audio_wrapper_get_name(void *type_data)
|
||||
{
|
||||
UNUSED_PARAMETER(type_data);
|
||||
return "transition_audio_wrapper";
|
||||
}
|
||||
|
||||
void *audio_wrapper_create(obs_data_t *settings, obs_source_t *source)
|
||||
{
|
||||
UNUSED_PARAMETER(settings);
|
||||
struct audio_wrapper_info *audio_wrapper = bzalloc(sizeof(struct audio_wrapper_info));
|
||||
audio_wrapper->source = source;
|
||||
return audio_wrapper;
|
||||
}
|
||||
|
||||
void audio_wrapper_destroy(void *data)
|
||||
{
|
||||
bfree(data);
|
||||
}
|
||||
|
||||
bool audio_wrapper_render(void *data, uint64_t *ts_out, struct obs_source_audio_mix *audio, uint32_t mixers, size_t channels,
|
||||
size_t sample_rate)
|
||||
{
|
||||
UNUSED_PARAMETER(sample_rate);
|
||||
struct audio_wrapper_info *aw = (struct audio_wrapper_info *)data;
|
||||
obs_source_t *source = aw->target(aw->param);
|
||||
if (!source)
|
||||
return false;
|
||||
|
||||
uint64_t timestamp = obs_source_get_audio_timestamp(source);
|
||||
if (!timestamp) {
|
||||
obs_source_release(source);
|
||||
return false;
|
||||
}
|
||||
if (!aw->mixers) {
|
||||
*ts_out = timestamp;
|
||||
obs_source_release(source);
|
||||
return true;
|
||||
}
|
||||
mixers &= aw->mixers(aw->param);
|
||||
if (mixers == 0) {
|
||||
*ts_out = timestamp;
|
||||
obs_source_release(source);
|
||||
return true;
|
||||
}
|
||||
struct obs_source_audio_mix child_audio;
|
||||
obs_source_get_audio_mix(source, &child_audio);
|
||||
for (size_t mix = 0; mix < MAX_AUDIO_MIXES; mix++) {
|
||||
if ((mixers & (1 << mix)) == 0)
|
||||
continue;
|
||||
|
||||
for (size_t ch = 0; ch < channels; ch++) {
|
||||
float *out = audio->output[mix].data[ch];
|
||||
float *in = child_audio.output[mix].data[ch];
|
||||
float *end = in + AUDIO_OUTPUT_FRAMES;
|
||||
while (in < end)
|
||||
*(out++) += *(in++);
|
||||
}
|
||||
}
|
||||
*ts_out = timestamp;
|
||||
obs_source_release(source);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void audio_wrapper_enum_sources(void *data, obs_source_enum_proc_t enum_callback, void *param, bool active)
|
||||
{
|
||||
UNUSED_PARAMETER(active);
|
||||
struct audio_wrapper_info *aw = (struct audio_wrapper_info *)data;
|
||||
obs_source_t *source = aw->target(aw->param);
|
||||
if (!source)
|
||||
return;
|
||||
|
||||
enum_callback(aw->source, source, param);
|
||||
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
void audio_wrapper_enum_active_sources(void *data, obs_source_enum_proc_t enum_callback, void *param)
|
||||
{
|
||||
audio_wrapper_enum_sources(data, enum_callback, param, true);
|
||||
}
|
||||
|
||||
void audio_wrapper_enum_all_sources(void *data, obs_source_enum_proc_t enum_callback, void *param)
|
||||
{
|
||||
audio_wrapper_enum_sources(data, enum_callback, param, false);
|
||||
}
|
||||
|
||||
struct obs_source_info audio_wrapper_source = {
|
||||
.id = "vertical_audio_wrapper_source",
|
||||
.type = OBS_SOURCE_TYPE_SCENE,
|
||||
.output_flags = OBS_SOURCE_COMPOSITE | OBS_SOURCE_CAP_DISABLED,
|
||||
.get_name = audio_wrapper_get_name,
|
||||
.create = audio_wrapper_create,
|
||||
.destroy = audio_wrapper_destroy,
|
||||
.audio_render = audio_wrapper_render,
|
||||
.enum_active_sources = audio_wrapper_enum_active_sources,
|
||||
.enum_all_sources = audio_wrapper_enum_all_sources,
|
||||
};
|
||||
17
audio-wrapper-source.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct audio_wrapper_info {
|
||||
obs_source_t *source;
|
||||
void *param;
|
||||
obs_source_t *(*target)(void *param);
|
||||
uint32_t (*mixers)(void *param);
|
||||
};
|
||||
|
||||
extern struct obs_source_info audio_wrapper_source;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
3
build-aux/.functions/log_debug
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
|
||||
|
||||
if (( _loglevel > 2 )) print -PR -e -- "${CI:+::debug::}%F{220}DEBUG: ${@}%f"
|
||||
3
build-aux/.functions/log_error
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
local icon=' ✖︎ '
|
||||
|
||||
print -u2 -PR "${CI:+::error::}%F{1} ${icon} %f ${@}"
|
||||
16
build-aux/.functions/log_group
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
autoload -Uz log_info
|
||||
|
||||
if (( ! ${+_log_group} )) typeset -g _log_group=0
|
||||
|
||||
if (( ${+CI} )) {
|
||||
if (( _log_group )) {
|
||||
print "::endgroup::"
|
||||
typeset -g _log_group=0
|
||||
}
|
||||
if (( # )) {
|
||||
print "::group::${@}"
|
||||
typeset -g _log_group=1
|
||||
}
|
||||
} else {
|
||||
if (( # )) log_info ${@}
|
||||
}
|
||||
7
build-aux/.functions/log_info
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
|
||||
|
||||
if (( _loglevel > 0 )) {
|
||||
local icon=' =>'
|
||||
|
||||
print -PR "%F{4} ${(r:5:)icon}%f %B${@}%b"
|
||||
}
|
||||
7
build-aux/.functions/log_output
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
|
||||
|
||||
if (( _loglevel > 0 )) {
|
||||
local icon=''
|
||||
|
||||
print -PR " ${(r:5:)icon} ${@}"
|
||||
}
|
||||
7
build-aux/.functions/log_status
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
|
||||
|
||||
if (( _loglevel > 0 )) {
|
||||
local icon=' >'
|
||||
|
||||
print -PR "%F{2} ${(r:5:)icon}%f ${@}"
|
||||
}
|
||||
7
build-aux/.functions/log_warning
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
if (( ! ${+_loglevel} )) typeset -g _loglevel=1
|
||||
|
||||
if (( _loglevel > 0 )) {
|
||||
local icon=' =>'
|
||||
|
||||
print -PR "${CI:+::warning::}%F{3} ${(r:5:)icon} ${@}%f"
|
||||
}
|
||||
17
build-aux/.functions/set_loglevel
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
autoload -Uz log_debug log_error
|
||||
|
||||
local -r _usage="Usage: %B${0}%b <loglevel>
|
||||
|
||||
Set log level, following levels are supported: 0 (quiet), 1 (normal), 2 (verbose), 3 (debug)"
|
||||
|
||||
if (( ! # )); then
|
||||
log_error 'Called without arguments.'
|
||||
log_output ${_usage}
|
||||
return 2
|
||||
elif (( ${1} >= 4 )); then
|
||||
log_error 'Called with loglevel > 3.'
|
||||
log_output ${_usage}
|
||||
fi
|
||||
|
||||
typeset -g -i -r _loglevel=${1}
|
||||
log_debug "Log level set to '${1}'"
|
||||
272
build-aux/.run-format.zsh
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
#!/usr/bin/env zsh
|
||||
|
||||
builtin emulate -L zsh
|
||||
setopt EXTENDED_GLOB
|
||||
setopt PUSHD_SILENT
|
||||
setopt ERR_EXIT
|
||||
setopt ERR_RETURN
|
||||
setopt NO_UNSET
|
||||
setopt PIPE_FAIL
|
||||
setopt NO_AUTO_PUSHD
|
||||
setopt NO_PUSHD_IGNORE_DUPS
|
||||
setopt FUNCTION_ARGZERO
|
||||
|
||||
## Enable for script debugging
|
||||
# setopt WARN_CREATE_GLOBAL
|
||||
# setopt WARN_NESTED_VAR
|
||||
# setopt XTRACE
|
||||
|
||||
autoload -Uz is-at-least && if ! is-at-least 5.8; then
|
||||
print -u2 -PR "%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."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
invoke_formatter() {
|
||||
if (( # < 1 )) {
|
||||
log_error "Usage invoke_formatter [formatter_name]"
|
||||
exit 2
|
||||
}
|
||||
|
||||
local formatter="${1}"
|
||||
shift
|
||||
local -a source_files=(${@})
|
||||
|
||||
case ${formatter} {
|
||||
clang)
|
||||
if (( ${+commands[clang-format-17]} )) {
|
||||
local formatter=clang-format-17
|
||||
} elif (( ${+commands[clang-format]} )) {
|
||||
local formatter=clang-format
|
||||
} else {
|
||||
log_error "No viable clang-format version found (required 17.0.3)"
|
||||
exit 2
|
||||
}
|
||||
|
||||
local -a formatter_version=($(${formatter} --version))
|
||||
|
||||
if ! is-at-least 17.0.3 ${formatter_version[-1]}; then
|
||||
log_error "clang-format is not version 17.0.3 or above (found ${formatter_version[-1]}."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! is-at-least ${formatter_version[-1]} 17.0.3; then
|
||||
log_error "clang-format is more recent than version 17.0.3 (found ${formatter_version[-1]})."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if (( ! #source_files )) source_files=(src/**/*.(c|cpp|h|hpp|m|mm)(.N))
|
||||
|
||||
local -a format_args=(-style=file -fallback-style=none)
|
||||
if (( _loglevel > 2 )) format_args+=(--verbose)
|
||||
|
||||
check_files() {
|
||||
local -i num_failures=0
|
||||
local -a source_files=($@)
|
||||
local file
|
||||
local -a format_args=(-style=file -fallback-style=none)
|
||||
if (( _loglevel > 2 )) format_args+=(--verbose)
|
||||
|
||||
local -a command=(${formatter} ${format_args})
|
||||
|
||||
for file (${source_files}) {
|
||||
if ! ${command} "${file}" | diff -q "${file}" - &> /dev/null; then
|
||||
log_error "${file} requires formatting changes."
|
||||
if (( fail_on_error == 2 )) return 2;
|
||||
num_failures=$(( num_failures + 1 ))
|
||||
fi
|
||||
}
|
||||
if (( num_failures && fail_on_error == 1 )) return 2
|
||||
}
|
||||
|
||||
format_files() {
|
||||
local -a source_files=($@)
|
||||
|
||||
if (( ${#source_files} )) {
|
||||
local -a format_args=(-style=file -fallback-style=none -i)
|
||||
if (( _loglevel > 2 )) format_args+=(--verbose)
|
||||
|
||||
"${formatter}" ${format_args} ${source_files}
|
||||
}
|
||||
}
|
||||
;;
|
||||
gersemi)
|
||||
local formatter=gersemi
|
||||
if (( ${+commands[gersemi]} )) {
|
||||
local gersemi_version=($(gersemi --version))
|
||||
|
||||
if ! is-at-least 0.12.0 ${gersemi_version[2]}; then
|
||||
log_error "gersemi is not version 0.12.0 or above (found ${gersemi_version[2]}."
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
if (( ! #source_files )) source_files=(CMakeLists.txt (cmake)/**/(CMakeLists.txt|*.cmake)(.N))
|
||||
|
||||
check_files() {
|
||||
local -i num_failures=0
|
||||
local -a source_files=($@)
|
||||
local file
|
||||
local -a command=(${formatter} -c --no-cache ${source_files})
|
||||
|
||||
if (( ${#source_files} )) {
|
||||
while read -r line; do
|
||||
local -a line_tokens=(${(z)line})
|
||||
if (( #line_tokens )) {
|
||||
file=${line_tokens[1]//*${project_root}\//}
|
||||
|
||||
log_error "${file} requires formatting changes."
|
||||
} else {
|
||||
log_error "${line}"
|
||||
}
|
||||
|
||||
if (( fail_on_error == 2 )) return 2
|
||||
num_failures=$(( num_failures + 1 ))
|
||||
done < <(${command} 2>&1)
|
||||
|
||||
if (( num_failures && fail_on_error == 1 )) return 2
|
||||
}
|
||||
}
|
||||
|
||||
format_files() {
|
||||
local -a source_files=($@)
|
||||
|
||||
if (( ${#source_files} )) {
|
||||
"${formatter}" -i ${source_files}
|
||||
}
|
||||
}
|
||||
;;
|
||||
swift)
|
||||
local formatter=swift-format
|
||||
if (( ${+commands[swift-format]} )) {
|
||||
local swift_format_version=$(swift-format --version)
|
||||
|
||||
if ! is-at-least 508.0.0 ${swift_format_version}; then
|
||||
log_error "swift-format is not version 508.0.0 or above (found ${swift_format_version})."
|
||||
exit 2
|
||||
fi
|
||||
} else {
|
||||
log_error "No viable swift-format version found (required 508.0.0)"
|
||||
exit 2
|
||||
}
|
||||
|
||||
if (( ! #source_files )) source_files=(src/**/*.swift(.N))
|
||||
|
||||
check_files() {
|
||||
local -i num_failures=0
|
||||
local -a source_files=($@)
|
||||
local file
|
||||
local -a format_args=()
|
||||
|
||||
local -a command=(${formatter} ${format_args})
|
||||
|
||||
for file (${source_files}) {
|
||||
if ! "${command}" "${file}" | diff -q "${file}" - &> /dev/null; then
|
||||
log_error "${file} requires formatting changes."
|
||||
if (( fail_on_error == 2 )) return 2;
|
||||
num_failures=$(( num_failures + 1 ))
|
||||
fi
|
||||
}
|
||||
if (( num_failures && fail_on_error == 1 )) return 2
|
||||
}
|
||||
|
||||
format_files() {
|
||||
local -a source_files=($@)
|
||||
|
||||
if (( ${#source_files} )) {
|
||||
local -a format_args=(-i)
|
||||
|
||||
"${formatter}" ${format_args} ${source_files}
|
||||
}
|
||||
}
|
||||
;;
|
||||
*) log_error "Invalid formatter specified: ${1}. Valid options are clang-format, gersemi, and swift-format."; exit 2 ;;
|
||||
}
|
||||
|
||||
|
||||
local file
|
||||
local -i num_failures=0
|
||||
if (( check_only )) {
|
||||
if (( ${+functions[check_files]} )) {
|
||||
check_files ${source_files}
|
||||
} else {
|
||||
log_error "No format check function defined for formatter '${formatter}'"
|
||||
exit 2
|
||||
}
|
||||
} else {
|
||||
if (( ${+functions[format_files]} )) {
|
||||
format_files ${source_files}
|
||||
} else {
|
||||
log_error "No format function defined for formatter '${formatter}'"
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
run_format() {
|
||||
if (( ! ${+SCRIPT_HOME} )) typeset -g SCRIPT_HOME=${ZSH_ARGZERO:A:h}
|
||||
if (( ! ${+FORMATTER_NAME} )) typeset -g FORMATTER_NAME=${${(s:-:)ZSH_ARGZERO:t:r}[2]}
|
||||
local project_root=${SCRIPT_HOME:A:h}
|
||||
|
||||
typeset -g host_os=${${(L)$(uname -s)}//darwin/macos}
|
||||
local -i fail_on_error=0
|
||||
local -i check_only=0
|
||||
local -i verbosity=1
|
||||
local -r _version='2.0.0'
|
||||
|
||||
fpath=("${SCRIPT_HOME}/.functions" ${fpath})
|
||||
autoload -Uz log_info log_error log_output set_loglevel log_status log_warning
|
||||
|
||||
local -r _usage="
|
||||
Usage: %B${functrace[1]%:*}%b <option> [SOURCE_FILES]
|
||||
|
||||
%BOptions%b:
|
||||
|
||||
%F{yellow} Formatting options%f
|
||||
-----------------------------------------------------------------------------
|
||||
%B-c | --check%b Check only, no actual formatting takes place
|
||||
|
||||
%F{yellow} Output options%f
|
||||
-----------------------------------------------------------------------------
|
||||
%B-v | --verbose%b Verbose (more detailed output)
|
||||
%B--fail-[never|error] Fail script never/on formatting change - default: %B%F{green}never%f%b
|
||||
%B--debug%b Debug (very detailed and added output)
|
||||
|
||||
%F{yellow} General options%f
|
||||
-----------------------------------------------------------------------------
|
||||
%B-h | --help%b Print this usage help
|
||||
%B-V | --version%b Print script version information"
|
||||
|
||||
local -a args
|
||||
while (( # )) {
|
||||
case ${1} {
|
||||
-c|--check) check_only=1; shift ;;
|
||||
-v|--verbose) (( verbosity += 1 )); shift ;;
|
||||
-h|--help) log_output ${_usage}; exit 0 ;;
|
||||
-V|--version) print -Pr "${_version}"; exit 0 ;;
|
||||
--debug) verbosity=3; shift ;;
|
||||
--fail-never)
|
||||
fail_on_error=0
|
||||
shift
|
||||
;;
|
||||
--fail-error)
|
||||
fail_on_error=1
|
||||
shift
|
||||
;;
|
||||
--fail-fast)
|
||||
fail_on_error=2
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
args+=($@)
|
||||
break
|
||||
;;
|
||||
}
|
||||
}
|
||||
|
||||
set -- ${(@)args}
|
||||
set_loglevel ${verbosity}
|
||||
|
||||
invoke_formatter ${FORMATTER_NAME} ${args}
|
||||
}
|
||||
|
||||
run_format ${@}
|
||||
1
build-aux/run-clang-format
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
.run-format.zsh
|
||||
1
build-aux/run-cmake-format
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
.run-format.zsh
|
||||
1
build-aux/run-gersemi
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
.run-format.zsh
|
||||
1
build-aux/run-swift-format
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
.run-format.zsh
|
||||
48
buildspec.json
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"obs-studio": {
|
||||
"version": "31.1.0",
|
||||
"baseUrl": "https://github.com/obsproject/obs-studio/archive/refs/tags",
|
||||
"label": "OBS sources",
|
||||
"hashes": {
|
||||
"macos": "ba903f3848cfabd9ea1205aea14b69e83a2eb43ad6f3f7cc80a6a900bce0e56e",
|
||||
"windows-x64": "ca4b7513bb0b34c708f1cc89016f6ed879b710c912a93387e9f83445c1fc68bb"
|
||||
}
|
||||
},
|
||||
"prebuilt": {
|
||||
"version": "2025-05-23",
|
||||
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
|
||||
"label": "Pre-Built obs-deps",
|
||||
"hashes": {
|
||||
"macos": "7b1448581b92c6f8d4e2f9b1b10eb318ebfc4df7255638e05bacb49b620e190d",
|
||||
"windows-x64": "88dc7bab50542b4b58194cdd5d7e6f4b73cde7c0d1ee7a040a6d9c9df05187b1"
|
||||
}
|
||||
},
|
||||
"qt6": {
|
||||
"version": "2025-05-23",
|
||||
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
|
||||
"label": "Pre-Built Qt6",
|
||||
"hashes": {
|
||||
"macos": "d4945fe719d31d353dc76298f58823d7891126e33af5029f763427ea9f80bfaa",
|
||||
"windows-x64": "bd80383dc799c0bba2f7fae793613b3fbd2cc3a3712b3d3b63b490721d187d9e"
|
||||
},
|
||||
"debugSymbols": {
|
||||
"windows-x64": "9c9a15c5dbe59eb6a1c31c06b7208f3c4403671fbdfaef341cac3beb7af24ea1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"platformConfig": {
|
||||
"macos": {
|
||||
"bundleId": "tv.aitum.vertical"
|
||||
}
|
||||
},
|
||||
"name": "vertical-canvas",
|
||||
"displayName": "Vertical",
|
||||
"version": "1.6.4",
|
||||
"author": "Aitum",
|
||||
"website": "https://aitum.tv",
|
||||
"email": "support@aitum.tv",
|
||||
"uuids": {
|
||||
"windowsApp": "9072EA15-785B-4BD9-8310-68CEECDA2117"
|
||||
}
|
||||
}
|
||||
82
cmake/common/bootstrap.cmake
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Plugin bootstrap module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Map fallback configurations for optimized build configurations
|
||||
# gersemi: off
|
||||
set(
|
||||
CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO
|
||||
RelWithDebInfo
|
||||
Release
|
||||
MinSizeRel
|
||||
None
|
||||
""
|
||||
)
|
||||
set(
|
||||
CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL
|
||||
MinSizeRel
|
||||
Release
|
||||
RelWithDebInfo
|
||||
None
|
||||
""
|
||||
)
|
||||
set(
|
||||
CMAKE_MAP_IMPORTED_CONFIG_RELEASE
|
||||
Release
|
||||
RelWithDebInfo
|
||||
MinSizeRel
|
||||
None
|
||||
""
|
||||
)
|
||||
# gersemi: on
|
||||
|
||||
# Add common module directories to default search path
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/common")
|
||||
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/buildspec.json" buildspec)
|
||||
|
||||
string(JSON _name GET ${buildspec} name)
|
||||
string(JSON _website GET ${buildspec} website)
|
||||
string(JSON _author GET ${buildspec} author)
|
||||
string(JSON _email GET ${buildspec} email)
|
||||
string(JSON _version GET ${buildspec} version)
|
||||
string(JSON _bundleId GET ${buildspec} platformConfig macos bundleId)
|
||||
string(JSON _uuidApp GET ${buildspec} uuids windowsApp)
|
||||
|
||||
set(PLUGIN_AUTHOR ${_author})
|
||||
set(PLUGIN_WEBSITE ${_website})
|
||||
set(PLUGIN_EMAIL ${_email})
|
||||
set(PLUGIN_VERSION ${_version})
|
||||
set(MACOS_BUNDLEID ${_bundleId})
|
||||
set(UUID_APP ${_uuidApp})
|
||||
|
||||
string(REPLACE "." ";" _version_canonical "${_version}")
|
||||
list(GET _version_canonical 0 PLUGIN_VERSION_MAJOR)
|
||||
list(GET _version_canonical 1 PLUGIN_VERSION_MINOR)
|
||||
list(GET _version_canonical 2 PLUGIN_VERSION_PATCH)
|
||||
unset(_version_canonical)
|
||||
|
||||
include(buildnumber)
|
||||
include(osconfig)
|
||||
|
||||
# Allow selection of common build types via UI
|
||||
if(NOT CMAKE_GENERATOR MATCHES "(Xcode|Visual Studio .+)")
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(
|
||||
CMAKE_BUILD_TYPE
|
||||
"RelWithDebInfo"
|
||||
CACHE STRING
|
||||
"OBS build type [Release, RelWithDebInfo, Debug, MinSizeRel]"
|
||||
FORCE
|
||||
)
|
||||
set_property(
|
||||
CACHE CMAKE_BUILD_TYPE
|
||||
PROPERTY STRINGS Release RelWithDebInfo Debug MinSizeRel
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Disable exports automatically going into the CMake package registry
|
||||
set(CMAKE_EXPORT_PACKAGE_REGISTRY FALSE)
|
||||
# Enable default inclusion of targets' source and binary directory
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR TRUE)
|
||||
30
cmake/common/buildnumber.cmake
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# CMake build number module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Define build number cache file
|
||||
set(
|
||||
_BUILD_NUMBER_CACHE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/.CMakeBuildNumber"
|
||||
CACHE INTERNAL
|
||||
"OBS build number cache file"
|
||||
)
|
||||
|
||||
# Read build number from cache file or manual override
|
||||
if(NOT DEFINED PLUGIN_BUILD_NUMBER)
|
||||
if(EXISTS "${_BUILD_NUMBER_CACHE}")
|
||||
file(READ "${_BUILD_NUMBER_CACHE}" PLUGIN_BUILD_NUMBER)
|
||||
math(EXPR PLUGIN_BUILD_NUMBER "${PLUGIN_BUILD_NUMBER}+1")
|
||||
else()
|
||||
if("$ENV{CI}")
|
||||
if("$ENV{GITHUB_RUN_ID}")
|
||||
set(PLUGIN_BUILD_NUMBER "$ENV{GITHUB_RUN_ID}")
|
||||
elseif("$ENV{GITLAB_RUN_ID}")
|
||||
set(PLUGIN_BUILD_NUMBER "$ENV{GITLAB_RUN_ID}")
|
||||
else()
|
||||
set(PLUGIN_BUILD_NUMBER "1")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
file(WRITE "${_BUILD_NUMBER_CACHE}" "${PLUGIN_BUILD_NUMBER}")
|
||||
endif()
|
||||
227
cmake/common/buildspec_common.cmake
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
# Common build dependencies module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# _check_deps_version: Checks for obs-deps VERSION file in prefix paths
|
||||
function(_check_deps_version version)
|
||||
set(found FALSE)
|
||||
|
||||
foreach(path IN LISTS CMAKE_PREFIX_PATH)
|
||||
if(EXISTS "${path}/share/obs-deps/VERSION")
|
||||
if(dependency STREQUAL qt6 AND NOT EXISTS "${path}/lib/cmake/Qt6/Qt6Config.cmake")
|
||||
set(found FALSE)
|
||||
continue()
|
||||
endif()
|
||||
|
||||
file(READ "${path}/share/obs-deps/VERSION" _check_version)
|
||||
string(REPLACE "\n" "" _check_version "${_check_version}")
|
||||
string(REPLACE "-" "." _check_version "${_check_version}")
|
||||
string(REPLACE "-" "." version "${version}")
|
||||
|
||||
if(_check_version VERSION_EQUAL version)
|
||||
set(found TRUE)
|
||||
break()
|
||||
elseif(_check_version VERSION_LESS version)
|
||||
message(
|
||||
AUTHOR_WARNING
|
||||
"Older ${label} version detected in ${path}: \n"
|
||||
"Found ${_check_version}, require ${version}"
|
||||
)
|
||||
list(REMOVE_ITEM CMAKE_PREFIX_PATH "${path}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${path}")
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH})
|
||||
continue()
|
||||
else()
|
||||
message(
|
||||
AUTHOR_WARNING
|
||||
"Newer ${label} version detected in ${path}: \n"
|
||||
"Found ${_check_version}, require ${version}"
|
||||
)
|
||||
set(found TRUE)
|
||||
break()
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
return(PROPAGATE found CMAKE_PREFIX_PATH)
|
||||
endfunction()
|
||||
|
||||
# _setup_obs_studio: Create obs-studio build project, then build libobs and obs-frontend-api
|
||||
function(_setup_obs_studio)
|
||||
if(NOT libobs_DIR)
|
||||
set(_is_fresh --fresh)
|
||||
endif()
|
||||
|
||||
if(OS_WINDOWS)
|
||||
set(_cmake_generator "${CMAKE_GENERATOR}")
|
||||
set(_cmake_arch "-A ${arch},version=${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
|
||||
set(_cmake_extra "-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION} -DCMAKE_ENABLE_SCRIPTING=OFF")
|
||||
elseif(OS_MACOS)
|
||||
set(_cmake_generator "Xcode")
|
||||
set(_cmake_arch "-DCMAKE_OSX_ARCHITECTURES:STRING='arm64;x86_64'")
|
||||
set(_cmake_extra "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Configure ${label} (${arch})")
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -S "${dependencies_dir}/${_obs_destination}" -B
|
||||
"${dependencies_dir}/${_obs_destination}/build_${arch}" -G ${_cmake_generator} "${_cmake_arch}"
|
||||
-DOBS_CMAKE_VERSION:STRING=3.0.0 -DENABLE_PLUGINS:BOOL=OFF -DENABLE_FRONTEND:BOOL=OFF
|
||||
-DOBS_VERSION_OVERRIDE:STRING=${_obs_version} "-DCMAKE_PREFIX_PATH='${CMAKE_PREFIX_PATH}'" ${_is_fresh}
|
||||
${_cmake_extra}
|
||||
RESULT_VARIABLE _process_result
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
OUTPUT_QUIET
|
||||
)
|
||||
message(STATUS "Configure ${label} (${arch}) - done")
|
||||
|
||||
message(STATUS "Build ${label} (Debug - ${arch})")
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" --build build_${arch} --target obs-frontend-api --config Debug --parallel
|
||||
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
|
||||
RESULT_VARIABLE _process_result
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
OUTPUT_QUIET
|
||||
)
|
||||
message(STATUS "Build ${label} (Debug - ${arch}) - done")
|
||||
|
||||
message(STATUS "Build ${label} (Release - ${arch})")
|
||||
execute_process(
|
||||
COMMAND "${CMAKE_COMMAND}" --build build_${arch} --target obs-frontend-api --config Release --parallel
|
||||
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
|
||||
RESULT_VARIABLE _process_result
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
OUTPUT_QUIET
|
||||
)
|
||||
message(STATUS "Build ${label} (Reelase - ${arch}) - done")
|
||||
|
||||
message(STATUS "Install ${label} (${arch})")
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" --install build_${arch} --component Development --config Debug --prefix "${dependencies_dir}"
|
||||
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
|
||||
RESULT_VARIABLE _process_result
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
OUTPUT_QUIET
|
||||
)
|
||||
execute_process(
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" --install build_${arch} --component Development --config Release --prefix "${dependencies_dir}"
|
||||
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
|
||||
RESULT_VARIABLE _process_result
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
OUTPUT_QUIET
|
||||
)
|
||||
message(STATUS "Install ${label} (${arch}) - done")
|
||||
endfunction()
|
||||
|
||||
# _check_dependencies: Fetch and extract pre-built OBS build dependencies
|
||||
function(_check_dependencies)
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/buildspec.json" buildspec)
|
||||
|
||||
string(JSON dependency_data GET ${buildspec} dependencies)
|
||||
|
||||
foreach(dependency IN LISTS dependencies_list)
|
||||
string(JSON data GET ${dependency_data} ${dependency})
|
||||
string(JSON version GET ${data} version)
|
||||
string(JSON hash GET ${data} hashes ${platform})
|
||||
string(JSON url GET ${data} baseUrl)
|
||||
string(JSON label GET ${data} label)
|
||||
string(JSON revision ERROR_VARIABLE error GET ${data} revision ${platform})
|
||||
|
||||
message(STATUS "Setting up ${label} (${arch})")
|
||||
|
||||
set(file "${${dependency}_filename}")
|
||||
set(destination "${${dependency}_destination}")
|
||||
string(REPLACE "VERSION" "${version}" file "${file}")
|
||||
string(REPLACE "VERSION" "${version}" destination "${destination}")
|
||||
string(REPLACE "ARCH" "${arch}" file "${file}")
|
||||
string(REPLACE "ARCH" "${arch}" destination "${destination}")
|
||||
if(revision)
|
||||
string(REPLACE "_REVISION" "_v${revision}" file "${file}")
|
||||
string(REPLACE "-REVISION" "-v${revision}" file "${file}")
|
||||
else()
|
||||
string(REPLACE "_REVISION" "" file "${file}")
|
||||
string(REPLACE "-REVISION" "" file "${file}")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${dependencies_dir}/.dependency_${dependency}_${arch}.sha256")
|
||||
file(
|
||||
READ
|
||||
"${dependencies_dir}/.dependency_${dependency}_${arch}.sha256"
|
||||
OBS_DEPENDENCY_${dependency}_${arch}_HASH
|
||||
)
|
||||
endif()
|
||||
|
||||
set(skip FALSE)
|
||||
if(dependency STREQUAL prebuilt OR dependency STREQUAL qt6)
|
||||
if(OBS_DEPENDENCY_${dependency}_${arch}_HASH STREQUAL ${hash})
|
||||
_check_deps_version(${version})
|
||||
|
||||
if(found)
|
||||
set(skip TRUE)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(skip)
|
||||
message(STATUS "Setting up ${label} (${arch}) - skipped")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
if(dependency STREQUAL obs-studio)
|
||||
set(url ${url}/${file})
|
||||
else()
|
||||
set(url ${url}/${version}/${file})
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${dependencies_dir}/${file}")
|
||||
message(STATUS "Downloading ${url}")
|
||||
file(DOWNLOAD "${url}" "${dependencies_dir}/${file}" STATUS download_status EXPECTED_HASH SHA256=${hash})
|
||||
|
||||
list(GET download_status 0 error_code)
|
||||
list(GET download_status 1 error_message)
|
||||
if(error_code GREATER 0)
|
||||
message(STATUS "Downloading ${url} - Failure")
|
||||
message(FATAL_ERROR "Unable to download ${url}, failed with error: ${error_message}")
|
||||
file(REMOVE "${dependencies_dir}/${file}")
|
||||
else()
|
||||
message(STATUS "Downloading ${url} - done")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT OBS_DEPENDENCY_${dependency}_${arch}_HASH STREQUAL ${hash})
|
||||
file(REMOVE_RECURSE "${dependencies_dir}/${destination}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${dependencies_dir}/${destination}")
|
||||
file(MAKE_DIRECTORY "${dependencies_dir}/${destination}")
|
||||
if(dependency STREQUAL obs-studio)
|
||||
file(ARCHIVE_EXTRACT INPUT "${dependencies_dir}/${file}" DESTINATION "${dependencies_dir}")
|
||||
else()
|
||||
file(ARCHIVE_EXTRACT INPUT "${dependencies_dir}/${file}" DESTINATION "${dependencies_dir}/${destination}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
file(WRITE "${dependencies_dir}/.dependency_${dependency}_${arch}.sha256" "${hash}")
|
||||
|
||||
if(dependency STREQUAL prebuilt)
|
||||
list(APPEND CMAKE_PREFIX_PATH "${dependencies_dir}/${destination}")
|
||||
elseif(dependency STREQUAL qt6)
|
||||
list(APPEND CMAKE_PREFIX_PATH "${dependencies_dir}/${destination}")
|
||||
elseif(dependency STREQUAL obs-studio)
|
||||
set(_obs_version ${version})
|
||||
set(_obs_destination "${destination}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${dependencies_dir}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Setting up ${label} (${arch}) - done")
|
||||
endforeach()
|
||||
|
||||
list(REMOVE_DUPLICATES CMAKE_PREFIX_PATH)
|
||||
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} CACHE PATH "CMake prefix search path" FORCE)
|
||||
|
||||
_setup_obs_studio()
|
||||
endfunction()
|
||||
25
cmake/common/ccache.cmake
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# OBS CMake ccache module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
if(NOT DEFINED CCACHE_PROGRAM)
|
||||
message(DEBUG "Trying to find ccache on build host")
|
||||
find_program(CCACHE_PROGRAM "ccache")
|
||||
mark_as_advanced(CCACHE_PROGRAM)
|
||||
endif()
|
||||
|
||||
if(CCACHE_PROGRAM)
|
||||
message(DEBUG "Trying to find ccache on build host - done")
|
||||
message(DEBUG "Ccache found as ${CCACHE_PROGRAM}")
|
||||
option(ENABLE_CCACHE "Enable compiler acceleration with ccache" OFF)
|
||||
|
||||
if(ENABLE_CCACHE)
|
||||
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
set(CMAKE_OBJC_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
set(CMAKE_OBJCXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
set(CMAKE_CUDA_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
|
||||
endif()
|
||||
else()
|
||||
message(DEBUG "Trying to find ccache on build host - skipped")
|
||||
endif()
|
||||
83
cmake/common/compiler_common.cmake
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# CMake common compiler options module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Set C and C++ language standards to C17 and C++17
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD_REQUIRED TRUE)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
|
||||
# Set symbols to be hidden by default for C and C++
|
||||
set(CMAKE_C_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN TRUE)
|
||||
|
||||
# clang options for C, C++, ObjC, and ObjC++
|
||||
set(
|
||||
_obs_clang_common_options
|
||||
-fno-strict-aliasing
|
||||
-Wno-trigraphs
|
||||
-Wno-missing-field-initializers
|
||||
-Wno-missing-prototypes
|
||||
-Werror=return-type
|
||||
-Wunreachable-code
|
||||
-Wquoted-include-in-framework-header
|
||||
-Wno-missing-braces
|
||||
-Wparentheses
|
||||
-Wswitch
|
||||
-Wno-unused-function
|
||||
-Wno-unused-label
|
||||
-Wunused-parameter
|
||||
-Wunused-variable
|
||||
-Wunused-value
|
||||
-Wempty-body
|
||||
-Wuninitialized
|
||||
-Wno-unknown-pragmas
|
||||
-Wfour-char-constants
|
||||
-Wconstant-conversion
|
||||
-Wno-conversion
|
||||
-Wint-conversion
|
||||
-Wbool-conversion
|
||||
-Wenum-conversion
|
||||
-Wnon-literal-null-conversion
|
||||
-Wsign-compare
|
||||
-Wshorten-64-to-32
|
||||
-Wpointer-sign
|
||||
-Wnewline-eof
|
||||
-Wno-implicit-fallthrough
|
||||
-Wdeprecated-declarations
|
||||
-Wno-sign-conversion
|
||||
-Winfinite-recursion
|
||||
-Wcomma
|
||||
-Wno-strict-prototypes
|
||||
-Wno-semicolon-before-method-body
|
||||
-Wformat-security
|
||||
-Wvla
|
||||
-Wno-error=shorten-64-to-32
|
||||
)
|
||||
|
||||
# clang options for C
|
||||
set(_obs_clang_c_options ${_obs_clang_common_options} -Wno-shadow -Wno-float-conversion)
|
||||
|
||||
# clang options for C++
|
||||
set(
|
||||
_obs_clang_cxx_options
|
||||
${_obs_clang_common_options}
|
||||
-Wno-non-virtual-dtor
|
||||
-Wno-overloaded-virtual
|
||||
-Wno-exit-time-destructors
|
||||
-Wno-shadow
|
||||
-Winvalid-offsetof
|
||||
-Wmove
|
||||
-Werror=block-capture-autoreleasing
|
||||
-Wrange-loop-analysis
|
||||
)
|
||||
|
||||
if(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
list(APPEND _obs_clang_cxx_options -fno-char8_t)
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED CMAKE_COMPILE_WARNING_AS_ERROR)
|
||||
set(CMAKE_COMPILE_WARNING_AS_ERROR ON)
|
||||
endif()
|
||||
49
cmake/common/helpers_common.cmake
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# CMake common helper functions module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# check_uuid: Helper function to check for valid UUID
|
||||
function(check_uuid uuid_string return_value)
|
||||
set(valid_uuid TRUE)
|
||||
# gersemi: off
|
||||
set(uuid_token_lengths 8 4 4 4 12)
|
||||
# gersemi: on
|
||||
set(token_num 0)
|
||||
|
||||
string(REPLACE "-" ";" uuid_tokens ${uuid_string})
|
||||
list(LENGTH uuid_tokens uuid_num_tokens)
|
||||
|
||||
if(uuid_num_tokens EQUAL 5)
|
||||
message(DEBUG "UUID ${uuid_string} is valid with 5 tokens.")
|
||||
foreach(uuid_token IN LISTS uuid_tokens)
|
||||
list(GET uuid_token_lengths ${token_num} uuid_target_length)
|
||||
string(LENGTH "${uuid_token}" uuid_actual_length)
|
||||
if(uuid_actual_length EQUAL uuid_target_length)
|
||||
string(REGEX MATCH "[0-9a-fA-F]+" uuid_hex_match ${uuid_token})
|
||||
if(NOT uuid_hex_match STREQUAL uuid_token)
|
||||
set(valid_uuid FALSE)
|
||||
break()
|
||||
endif()
|
||||
else()
|
||||
set(valid_uuid FALSE)
|
||||
break()
|
||||
endif()
|
||||
math(EXPR token_num "${token_num}+1")
|
||||
endforeach()
|
||||
else()
|
||||
set(valid_uuid FALSE)
|
||||
endif()
|
||||
message(DEBUG "UUID ${uuid_string} valid: ${valid_uuid}")
|
||||
set(${return_value} ${valid_uuid} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/plugin-support.c.in")
|
||||
configure_file(src/plugin-support.c.in plugin-support.c @ONLY)
|
||||
add_library(plugin-support STATIC)
|
||||
target_sources(plugin-support PRIVATE plugin-support.c PUBLIC src/plugin-support.h)
|
||||
target_include_directories(plugin-support PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
if(OS_LINUX OR OS_FREEBSD OR OS_OPENBSD)
|
||||
# add fPIC on Linux to prevent shared object errors
|
||||
set_property(TARGET plugin-support PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
endif()
|
||||
20
cmake/common/osconfig.cmake
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# CMake operating system bootstrap module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
|
||||
set(CMAKE_C_EXTENSIONS FALSE)
|
||||
set(CMAKE_CXX_EXTENSIONS FALSE)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows")
|
||||
set(OS_WINDOWS TRUE)
|
||||
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
|
||||
set(CMAKE_C_EXTENSIONS FALSE)
|
||||
set(CMAKE_CXX_EXTENSIONS FALSE)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos")
|
||||
set(OS_MACOS TRUE)
|
||||
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "Linux|FreeBSD|OpenBSD")
|
||||
set(CMAKE_CXX_EXTENSIONS FALSE)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/linux")
|
||||
string(TOUPPER "${CMAKE_HOST_SYSTEM_NAME}" _SYSTEM_NAME_U)
|
||||
set(OS_${_SYSTEM_NAME_U} TRUE)
|
||||
endif()
|
||||
78
cmake/linux/compilerconfig.cmake
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# CMake Linux compiler configuration module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(ccache)
|
||||
include(compiler_common)
|
||||
|
||||
option(ENABLE_COMPILER_TRACE "Enable Clang time-trace (required Clang and Ninja)" OFF)
|
||||
mark_as_advanced(ENABLE_COMPILER_TRACE)
|
||||
|
||||
# gcc options for C
|
||||
set(
|
||||
_obs_gcc_c_options
|
||||
-fno-strict-aliasing
|
||||
-fopenmp-simd
|
||||
-Wdeprecated-declarations
|
||||
-Wempty-body
|
||||
-Wenum-conversion
|
||||
-Werror=return-type
|
||||
-Wextra
|
||||
-Wformat
|
||||
-Wformat-security
|
||||
-Wno-conversion
|
||||
-Wno-float-conversion
|
||||
-Wno-implicit-fallthrough
|
||||
-Wno-missing-braces
|
||||
-Wno-missing-field-initializers
|
||||
-Wno-shadow
|
||||
-Wno-sign-conversion
|
||||
-Wno-trigraphs
|
||||
-Wno-unknown-pragmas
|
||||
-Wno-unused-function
|
||||
-Wno-unused-label
|
||||
-Wparentheses
|
||||
-Wuninitialized
|
||||
-Wunreachable-code
|
||||
-Wunused-parameter
|
||||
-Wunused-value
|
||||
-Wunused-variable
|
||||
-Wvla
|
||||
)
|
||||
|
||||
add_compile_options(
|
||||
-fopenmp-simd
|
||||
"$<$<COMPILE_LANG_AND_ID:C,GNU>:${_obs_gcc_c_options}>"
|
||||
"$<$<COMPILE_LANG_AND_ID:C,GNU>:-Wint-conversion;-Wno-missing-prototypes;-Wno-strict-prototypes;-Wpointer-sign>"
|
||||
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:${_obs_gcc_c_options}>"
|
||||
"$<$<COMPILE_LANG_AND_ID:CXX,GNU>:-Winvalid-offsetof;-Wno-overloaded-virtual>"
|
||||
"$<$<COMPILE_LANG_AND_ID:C,Clang>:${_obs_clang_c_options}>"
|
||||
"$<$<COMPILE_LANG_AND_ID:CXX,Clang>:${_obs_clang_cxx_options}>"
|
||||
)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL GNU)
|
||||
# * Disable false-positive warning in GCC 12.1.0 and later
|
||||
# * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105562
|
||||
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.1.0)
|
||||
add_compile_options(-Wno-error=maybe-uninitialized)
|
||||
endif()
|
||||
|
||||
# * Add warning for infinite recursion (added in GCC 12)
|
||||
# * Also disable warnings for stringop-overflow due to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106297
|
||||
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||
add_compile_options(-Winfinite-recursion -Wno-stringop-overflow)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
add_compile_options(-Wno-error=type-limits)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Enable compiler and build tracing (requires Ninja generator)
|
||||
if(ENABLE_COMPILER_TRACE AND CMAKE_GENERATOR STREQUAL "Ninja")
|
||||
add_compile_options($<$<COMPILE_LANG_AND_ID:C,Clang>:-ftime-trace> $<$<COMPILE_LANG_AND_ID:CXX,Clang>:-ftime-trace>)
|
||||
else()
|
||||
set(ENABLE_COMPILER_TRACE OFF CACHE STRING "Enable Clang time-trace (required Clang and Ninja)" FORCE)
|
||||
endif()
|
||||
|
||||
add_compile_definitions($<$<CONFIG:DEBUG>:DEBUG> $<$<CONFIG:DEBUG>:_DEBUG> SIMDE_ENABLE_OPENMP)
|
||||
88
cmake/linux/defaults.cmake
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# CMake Linux defaults module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Set default installation directories
|
||||
include(GNUInstallDirs)
|
||||
|
||||
if(CMAKE_INSTALL_LIBDIR MATCHES "(CMAKE_SYSTEM_PROCESSOR)")
|
||||
string(REPLACE "CMAKE_SYSTEM_PROCESSOR" "${CMAKE_SYSTEM_PROCESSOR}" CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
|
||||
endif()
|
||||
|
||||
# Enable find_package targets to become globally available targets
|
||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
|
||||
|
||||
set(CPACK_PACKAGE_NAME "${CMAKE_PROJECT_NAME}")
|
||||
set(CPACK_PACKAGE_VERSION "${CMAKE_PROJECT_VERSION}")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CMAKE_C_LIBRARY_ARCHITECTURE}")
|
||||
|
||||
set(CPACK_GENERATOR "DEB")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${PLUGIN_EMAIL}")
|
||||
set(CPACK_SET_DESTDIR ON)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.25.0 OR NOT CMAKE_CROSSCOMPILING)
|
||||
set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON)
|
||||
endif()
|
||||
|
||||
set(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/release")
|
||||
|
||||
set(CPACK_SOURCE_GENERATOR "TXZ")
|
||||
set(
|
||||
CPACK_SOURCE_IGNORE_FILES
|
||||
".*~$"
|
||||
\\.git/
|
||||
\\.github/
|
||||
\\.gitignore
|
||||
\\.ccache/
|
||||
build_.*
|
||||
cmake/\\.CMakeBuildNumber
|
||||
release/
|
||||
)
|
||||
|
||||
set(CPACK_VERBATIM_VARIABLES YES)
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-source")
|
||||
set(CPACK_ARCHIVE_THREADS 0)
|
||||
|
||||
include(CPack)
|
||||
|
||||
find_package(libobs QUIET)
|
||||
|
||||
if(NOT TARGET OBS::libobs)
|
||||
find_package(LibObs REQUIRED)
|
||||
add_library(OBS::libobs ALIAS libobs)
|
||||
|
||||
if(ENABLE_FRONTEND_API)
|
||||
find_path(
|
||||
obs-frontend-api_INCLUDE_DIR
|
||||
NAMES obs-frontend-api.h
|
||||
PATHS /usr/include /usr/local/include
|
||||
PATH_SUFFIXES obs
|
||||
)
|
||||
|
||||
find_library(obs-frontend-api_LIBRARY NAMES obs-frontend-api PATHS /usr/lib /usr/local/lib)
|
||||
|
||||
if(obs-frontend-api_LIBRARY)
|
||||
if(NOT TARGET OBS::obs-frontend-api)
|
||||
if(IS_ABSOLUTE "${obs-frontend-api_LIBRARY}")
|
||||
add_library(OBS::obs-frontend-api UNKNOWN IMPORTED)
|
||||
set_property(TARGET OBS::obs-frontend-api PROPERTY IMPORTED_LOCATION "${obs-frontend-api_LIBRARY}")
|
||||
else()
|
||||
add_library(OBS::obs-frontend-api INTERFACE IMPORTED)
|
||||
set_property(TARGET OBS::obs-frontend-api PROPERTY IMPORTED_LIBNAME "${obs-frontend-api_LIBRARY}")
|
||||
endif()
|
||||
|
||||
set_target_properties(
|
||||
OBS::obs-frontend-api
|
||||
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${obs-frontend-api_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
macro(find_package)
|
||||
if(NOT "${ARGV0}" STREQUAL libobs AND NOT "${ARGV0}" STREQUAL obs-frontend-api)
|
||||
_find_package(${ARGV})
|
||||
endif()
|
||||
endmacro()
|
||||
endif()
|
||||
106
cmake/linux/helpers.cmake
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# CMake Linux helper functions module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(helpers_common)
|
||||
|
||||
# set_target_properties_plugin: Set target properties for use in obs-studio
|
||||
function(set_target_properties_plugin target)
|
||||
set(options "")
|
||||
set(oneValueArgs "")
|
||||
set(multiValueArgs PROPERTIES)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 _STPO "${options}" "${oneValueArgs}" "${multiValueArgs}")
|
||||
|
||||
message(DEBUG "Setting additional properties for target ${target}...")
|
||||
|
||||
while(_STPO_PROPERTIES)
|
||||
list(POP_FRONT _STPO_PROPERTIES key value)
|
||||
set_property(TARGET ${target} PROPERTY ${key} "${value}")
|
||||
endwhile()
|
||||
|
||||
set_target_properties(
|
||||
${target}
|
||||
PROPERTIES VERSION ${PLUGIN_VERSION} SOVERSION ${PLUGIN_VERSION_MAJOR} PREFIX ""
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS ${target}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/obs-plugins
|
||||
)
|
||||
|
||||
if(TARGET plugin-support)
|
||||
target_link_libraries(${target} PRIVATE plugin-support)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>" "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>"
|
||||
COMMENT "Copy ${target} to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
target_install_resources(${target})
|
||||
|
||||
get_target_property(target_sources ${target} SOURCES)
|
||||
set(target_ui_files ${target_sources})
|
||||
list(FILTER target_ui_files INCLUDE REGEX ".+\\.(ui|qrc)")
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "UI Files" FILES ${target_ui_files})
|
||||
endfunction()
|
||||
|
||||
# Helper function to add resources into bundle
|
||||
function(target_install_resources target)
|
||||
message(DEBUG "Installing resources for target ${target}...")
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data")
|
||||
file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*")
|
||||
foreach(data_file IN LISTS data_files)
|
||||
cmake_path(
|
||||
RELATIVE_PATH
|
||||
data_file
|
||||
BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
||||
OUTPUT_VARIABLE relative_path
|
||||
)
|
||||
cmake_path(GET relative_path PARENT_PATH relative_path)
|
||||
target_sources(${target} PRIVATE "${data_file}")
|
||||
source_group("Resources/${relative_path}" FILES "${data_file}")
|
||||
endforeach()
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-plugins/${target}
|
||||
USE_SOURCE_PERMISSIONS
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/data"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMENT "Copy ${target} resources to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper function to add a specific resource to a bundle
|
||||
function(target_add_resource target resource)
|
||||
message(DEBUG "Add resource '${resource}' to target ${target} at destination '${target_destination}'...")
|
||||
|
||||
install(FILES "${resource}" DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/obs/obs-plugins/${target})
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy "${resource}" "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMENT "Copy ${target} resource ${resource} to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
source_group("Resources" FILES "${resource}")
|
||||
endfunction()
|
||||
56
cmake/linux/toolchains/aarch64-linux-clang.cmake
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
set(CMAKE_C_COMPILER /usr/bin/clang)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
|
||||
|
||||
set(CMAKE_C_COMPILER_TARGET aarch64-linux-gnu)
|
||||
set(CMAKE_CXX_COMPILER_TARGET aarch64-linux-gnu)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(PKG_CONFIG_EXECUTABLE
|
||||
/usr/bin/aarch64-linux-gnu-pkg-config
|
||||
CACHE FILEPATH "pkg-config executable")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-ranlib
|
||||
OUTPUT_VARIABLE CMAKE_RANLIB
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-ar
|
||||
OUTPUT_VARIABLE CMAKE_LLVM_AR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-readelf
|
||||
OUTPUT_VARIABLE READELF
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-objcopy
|
||||
OUTPUT_VARIABLE CMAKE_LLVM_OBJCOPY
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-objdump
|
||||
OUTPUT_VARIABLE CMAKE_LLVM_OBJDUMP
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
set(CMAKE_AR
|
||||
"${CMAKE_LLVM_AR}"
|
||||
CACHE INTERNAL "${CMAKE_SYSTEM_NAME} ar" FORCE)
|
||||
set(CMAKE_OBJCOPY
|
||||
"${CMAKE_LLVM_OBJCOPY}"
|
||||
CACHE INTERNAL "${CMAKE_SYSTEM_NAME} objcopy" FORCE)
|
||||
set(CMAKE_OBJDUMP
|
||||
"${CMAKE_LLVM_OBJDUMP}"
|
||||
CACHE INTERNAL "${CMAKE_SYSTEM_NAME} objdump" FORCE)
|
||||
|
||||
set(CPACK_READELF_EXECUTABLE "${READELF}")
|
||||
set(CPACK_OBJCOPY_EXECUTABLE "${CMAKE_LLVM_OBJCOPY}")
|
||||
set(CPACK_OBJDUMP_EXECUTABLE "${CMAKE_LLVM_OBJDUMP}")
|
||||
set(CPACK_PACKAGE_ARCHITECTURE arm64)
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64)
|
||||
20
cmake/linux/toolchains/aarch64-linux-gcc.cmake
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
|
||||
set(PKG_CONFIG_EXECUTABLE
|
||||
/usr/bin/aarch64-linux-gnu-pkg-config
|
||||
CACHE FILEPATH "pkg-config executable")
|
||||
|
||||
set(CPACK_READELF_EXECUTABLE /usr/bin/aarch64-linux-gnu-readelf)
|
||||
set(CPACK_OBJCOPY_EXECUTABLE /usr/bin/aarch64-linux-gnu-objcopy)
|
||||
set(CPACK_OBJDUMP_EXECUTABLE /usr/bin/aarch64-linux-gnu-objdump)
|
||||
set(CPACK_PACKAGE_ARCHITECTURE arm64)
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64)
|
||||
56
cmake/linux/toolchains/x86_64-linux-clang.cmake
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
set(CMAKE_C_COMPILER /usr/bin/clang)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/clang++)
|
||||
|
||||
set(CMAKE_C_COMPILER_TARGET x86_64-linux-gnu)
|
||||
set(CMAKE_CXX_COMPILER_TARGET x86_64-linux-gnu)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-linux-gnu)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(PKG_CONFIG_EXECUTABLE
|
||||
/usr/bin/x86_64-linux-gnu-pkg-config
|
||||
CACHE FILEPATH "pkg-config executable")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-ranlib
|
||||
OUTPUT_VARIABLE CMAKE_RANLIB
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-ar
|
||||
OUTPUT_VARIABLE CMAKE_LLVM_AR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-readelf
|
||||
OUTPUT_VARIABLE READELF
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-objcopy
|
||||
OUTPUT_VARIABLE CMAKE_LLVM_OBJCOPY
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -print-prog-name=llvm-objdump
|
||||
OUTPUT_VARIABLE CMAKE_LLVM_OBJDUMP
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
set(CMAKE_AR
|
||||
"${CMAKE_LLVM_AR}"
|
||||
CACHE INTERNAL "${CMAKE_SYSTEM_NAME} ar" FORCE)
|
||||
set(CMAKE_OBJCOPY
|
||||
"${CMAKE_LLVM_OBJCOPY}"
|
||||
CACHE INTERNAL "${CMAKE_SYSTEM_NAME} objcopy" FORCE)
|
||||
set(CMAKE_OBJDUMP
|
||||
"${CMAKE_LLVM_OBJDUMP}"
|
||||
CACHE INTERNAL "${CMAKE_SYSTEM_NAME} objdump" FORCE)
|
||||
|
||||
set(CPACK_READELF_EXECUTABLE "${READELF}")
|
||||
set(CPACK_OBJCOPY_EXECUTABLE "${CMAKE_LLVM_OBJCOPY}")
|
||||
set(CPACK_OBJDUMP_EXECUTABLE "${CMAKE_LLVM_OBJDUMP}")
|
||||
set(CPACK_PACKAGE_ARCHITECTURE x86_64)
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE x86_64)
|
||||
20
cmake/linux/toolchains/x86_64-linux-gcc.cmake
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||
set(CMAKE_CROSSCOMPILING TRUE)
|
||||
|
||||
set(CMAKE_C_COMPILER /usr/bin/x86_64-linux-gnu-gcc)
|
||||
set(CMAKE_CXX_COMPILER /usr/bin/x86_64-linux-gnu-g++)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-linux-gnu)
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
|
||||
set(PKG_CONFIG_EXECUTABLE
|
||||
/usr/bin/x86_64-linux-gnu-pkg-config
|
||||
CACHE FILEPATH "pkg-config executable")
|
||||
|
||||
set(CPACK_READELF_EXECUTABLE /usr/bin/x86_64-linux-gnu-readelf)
|
||||
set(CPACK_OBJCOPY_EXECUTABLE /usr/bin/x86_64-linux-gnu-objcopy)
|
||||
set(CPACK_OBJDUMP_EXECUTABLE /usr/bin/x86_64-linux-gnu-objdump)
|
||||
set(CPACK_PACKAGE_ARCHITECTURE x86_64)
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE x86_64)
|
||||
35
cmake/macos/buildspec.cmake
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# CMake macOS build dependencies module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(buildspec_common)
|
||||
|
||||
# _check_dependencies_macos: Set up macOS slice for _check_dependencies
|
||||
function(_check_dependencies_macos)
|
||||
set(arch universal)
|
||||
set(platform macos)
|
||||
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/buildspec.json" buildspec)
|
||||
|
||||
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")
|
||||
set(prebuilt_filename "macos-deps-VERSION-ARCH_REVISION.tar.xz")
|
||||
set(prebuilt_destination "obs-deps-VERSION-ARCH")
|
||||
set(qt6_filename "macos-deps-qt6-VERSION-ARCH-REVISION.tar.xz")
|
||||
set(qt6_destination "obs-deps-qt6-VERSION-ARCH")
|
||||
set(obs-studio_filename "VERSION.tar.gz")
|
||||
set(obs-studio_destination "obs-studio-VERSION")
|
||||
set(dependencies_list prebuilt qt6 obs-studio)
|
||||
|
||||
_check_dependencies()
|
||||
|
||||
execute_process(
|
||||
COMMAND "xattr" -r -d com.apple.quarantine "${dependencies_dir}"
|
||||
RESULT_VARIABLE result
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_FRAMEWORK_PATH "${dependencies_dir}/Frameworks")
|
||||
set(CMAKE_FRAMEWORK_PATH ${CMAKE_FRAMEWORK_PATH} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
_check_dependencies_macos()
|
||||
109
cmake/macos/compilerconfig.cmake
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# OBS CMake macOS compiler configuration module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
option(ENABLE_COMPILER_TRACE "Enable clang time-trace" OFF)
|
||||
mark_as_advanced(ENABLE_COMPILER_TRACE)
|
||||
|
||||
if(NOT XCODE)
|
||||
message(FATAL_ERROR "Building OBS Studio on macOS requires Xcode generator.")
|
||||
endif()
|
||||
|
||||
include(ccache)
|
||||
include(compiler_common)
|
||||
|
||||
add_compile_options("$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:-fopenmp-simd>")
|
||||
|
||||
# Enable selection between arm64 and x86_64 targets
|
||||
if(NOT CMAKE_OSX_ARCHITECTURES)
|
||||
set(CMAKE_OSX_ARCHITECTURES arm64 CACHE STRING "Build architectures for macOS" FORCE)
|
||||
endif()
|
||||
set_property(CACHE CMAKE_OSX_ARCHITECTURES PROPERTY STRINGS arm64 x86_64)
|
||||
|
||||
# Ensure recent enough Xcode and platform SDK
|
||||
function(check_sdk_requirements)
|
||||
set(obs_macos_minimum_sdk 15.0) # Keep in sync with Xcode
|
||||
set(obs_macos_minimum_xcode 16.0) # Keep in sync with SDK
|
||||
execute_process(
|
||||
COMMAND xcrun --sdk macosx --show-sdk-platform-version
|
||||
OUTPUT_VARIABLE obs_macos_current_sdk
|
||||
RESULT_VARIABLE result
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if(NOT result EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Failed to fetch macOS SDK version. "
|
||||
"Ensure that the macOS SDK is installed and that xcode-select points at the Xcode developer directory."
|
||||
)
|
||||
endif()
|
||||
message(DEBUG "macOS SDK version: ${obs_macos_current_sdk}")
|
||||
if(obs_macos_current_sdk VERSION_LESS obs_macos_minimum_sdk)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Your macOS SDK version (${obs_macos_current_sdk}) is too low. "
|
||||
"The macOS ${obs_macos_minimum_sdk} SDK (Xcode ${obs_macos_minimum_xcode}) is required to build OBS."
|
||||
)
|
||||
endif()
|
||||
execute_process(COMMAND xcrun --find xcodebuild OUTPUT_VARIABLE obs_macos_xcodebuild RESULT_VARIABLE result)
|
||||
if(NOT result EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Xcode was not found. "
|
||||
"Ensure you have installed Xcode and that xcode-select points at the Xcode developer directory."
|
||||
)
|
||||
endif()
|
||||
message(DEBUG "Path to xcodebuild binary: ${obs_macos_xcodebuild}")
|
||||
if(XCODE_VERSION VERSION_LESS obs_macos_minimum_xcode)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Your Xcode version (${XCODE_VERSION}) is too low. Xcode ${obs_macos_minimum_xcode} is required to build OBS."
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
check_sdk_requirements()
|
||||
|
||||
# Enable dSYM generator for release builds
|
||||
string(APPEND CMAKE_C_FLAGS_RELEASE " -g")
|
||||
string(APPEND CMAKE_CXX_FLAGS_RELEASE " -g")
|
||||
string(APPEND CMAKE_OBJC_FLAGS_RELEASE " -g")
|
||||
string(APPEND CMAKE_OBJCXX_FLAGS_RELEASE " -g")
|
||||
|
||||
# Default ObjC compiler options used by Xcode:
|
||||
#
|
||||
# * -Wno-implicit-atomic-properties
|
||||
# * -Wno-objc-interface-ivars
|
||||
# * -Warc-repeated-use-of-weak
|
||||
# * -Wno-arc-maybe-repeated-use-of-weak
|
||||
# * -Wimplicit-retain-self
|
||||
# * -Wduplicate-method-match
|
||||
# * -Wshadow
|
||||
# * -Wfloat-conversion
|
||||
# * -Wobjc-literal-conversion
|
||||
# * -Wno-selector
|
||||
# * -Wno-strict-selector-match
|
||||
# * -Wundeclared-selector
|
||||
# * -Wdeprecated-implementations
|
||||
# * -Wprotocol
|
||||
# * -Werror=block-capture-autoreleasing
|
||||
# * -Wrange-loop-analysis
|
||||
|
||||
# Default ObjC++ compiler options used by Xcode:
|
||||
#
|
||||
# * -Wno-non-virtual-dtor
|
||||
|
||||
add_compile_definitions(
|
||||
$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:$<$<CONFIG:DEBUG>:DEBUG>>
|
||||
$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:$<$<CONFIG:DEBUG>:_DEBUG>>
|
||||
$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:SIMDE_ENABLE_OPENMP>
|
||||
)
|
||||
|
||||
if(ENABLE_COMPILER_TRACE)
|
||||
add_compile_options(
|
||||
$<$<NOT:$<COMPILE_LANGUAGE:Swift>>:-ftime-trace>
|
||||
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -debug-time-expression-type-checking>"
|
||||
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-Xfrontend -debug-time-function-bodies>"
|
||||
)
|
||||
add_link_options(LINKER:-print_statistics)
|
||||
endif()
|
||||
39
cmake/macos/defaults.cmake
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# CMake macOS defaults module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Set empty codesigning team if not specified as cache variable
|
||||
if(NOT CODESIGN_TEAM)
|
||||
set(CODESIGN_TEAM "" CACHE STRING "OBS code signing team for macOS" FORCE)
|
||||
|
||||
# Set ad-hoc codesigning identity if not specified as cache variable
|
||||
if(NOT CODESIGN_IDENTITY)
|
||||
set(CODESIGN_IDENTITY "-" CACHE STRING "OBS code signing identity for macOS" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(xcode)
|
||||
|
||||
include(buildspec)
|
||||
|
||||
# Use Applications directory as default install destination
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(
|
||||
CMAKE_INSTALL_PREFIX
|
||||
"$ENV{HOME}/Library/Application Support/obs-studio/plugins"
|
||||
CACHE STRING
|
||||
"Default plugin installation directory"
|
||||
FORCE
|
||||
)
|
||||
endif()
|
||||
|
||||
# Enable find_package targets to become globally available targets
|
||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
|
||||
# Enable RPATH support for generated binaries
|
||||
set(CMAKE_MACOSX_RPATH TRUE)
|
||||
# Use RPATHs from build tree _in_ the build tree
|
||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
# Do not add default linker search paths to RPATH
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
|
||||
# Use common bundle-relative RPATH for installed targets
|
||||
set(CMAKE_INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
102
cmake/macos/helpers.cmake
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# CMake macOS helper functions module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(helpers_common)
|
||||
|
||||
# set_target_properties_obs: Set target properties for use in obs-studio
|
||||
function(set_target_properties_plugin target)
|
||||
set(options "")
|
||||
set(oneValueArgs "")
|
||||
set(multiValueArgs PROPERTIES)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 _STPO "${options}" "${oneValueArgs}" "${multiValueArgs}")
|
||||
|
||||
message(DEBUG "Setting additional properties for target ${target}...")
|
||||
|
||||
while(_STPO_PROPERTIES)
|
||||
list(POP_FRONT _STPO_PROPERTIES key value)
|
||||
set_property(TARGET ${target} PROPERTY ${key} "${value}")
|
||||
endwhile()
|
||||
|
||||
string(TIMESTAMP CURRENT_YEAR "%Y")
|
||||
set_target_properties(
|
||||
${target}
|
||||
PROPERTIES
|
||||
BUNDLE TRUE
|
||||
BUNDLE_EXTENSION plugin
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME ${target}
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER ${MACOS_BUNDLEID}
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION ${PLUGIN_BUILD_NUMBER}
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION ${PLUGIN_VERSION}
|
||||
XCODE_ATTRIBUTE_GENERATE_INFOPLIST_FILE YES
|
||||
XCODE_ATTRIBUTE_INFOPLIST_FILE ""
|
||||
XCODE_ATTRIBUTE_INFOPLIST_KEY_CFBundleDisplayName ${target}
|
||||
XCODE_ATTRIBUTE_INFOPLIST_KEY_NSHumanReadableCopyright "(c) ${CURRENT_YEAR} ${PLUGIN_AUTHOR}"
|
||||
XCODE_ATTRIBUTE_INSTALL_PATH "$(USER_LIBRARY_DIR)/Application Support/obs-studio/plugins"
|
||||
)
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist")
|
||||
set_target_properties(
|
||||
${target}
|
||||
PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(TARGET plugin-support)
|
||||
target_link_libraries(${target} PRIVATE plugin-support)
|
||||
endif()
|
||||
|
||||
target_install_resources(${target})
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_directory "$<TARGET_BUNDLE_DIR:${target}>"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/$<TARGET_BUNDLE_DIR_NAME:${target}>"
|
||||
COMMENT "Copy ${target} to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
get_target_property(target_sources ${target} SOURCES)
|
||||
set(target_ui_files ${target_sources})
|
||||
list(FILTER target_ui_files INCLUDE REGEX ".+\\.(ui|qrc)")
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "UI Files" FILES ${target_ui_files})
|
||||
|
||||
install(TARGETS ${target} LIBRARY DESTINATION .)
|
||||
install(FILES "$<TARGET_BUNDLE_DIR:${target}>.dsym" CONFIGURATIONS Release DESTINATION . OPTIONAL)
|
||||
|
||||
configure_file(cmake/macos/resources/distribution.in "${CMAKE_CURRENT_BINARY_DIR}/distribution" @ONLY)
|
||||
configure_file(cmake/macos/resources/create-package.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/create-package.cmake" @ONLY)
|
||||
install(SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/create-package.cmake")
|
||||
endfunction()
|
||||
|
||||
# target_install_resources: Helper function to add resources into bundle
|
||||
function(target_install_resources target)
|
||||
message(DEBUG "Installing resources for target ${target}...")
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data")
|
||||
file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*")
|
||||
list(FILTER data_files EXCLUDE REGEX "\\.DS_Store$")
|
||||
foreach(data_file IN LISTS data_files)
|
||||
cmake_path(
|
||||
RELATIVE_PATH
|
||||
data_file
|
||||
BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
||||
OUTPUT_VARIABLE relative_path
|
||||
)
|
||||
cmake_path(GET relative_path PARENT_PATH relative_path)
|
||||
target_sources(${target} PRIVATE "${data_file}")
|
||||
set_property(SOURCE "${data_file}" PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${relative_path}")
|
||||
source_group("Resources/${relative_path}" FILES "${data_file}")
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# target_add_resource: Helper function to add a specific resource to a bundle
|
||||
function(target_add_resource target resource)
|
||||
message(DEBUG "Add resource ${resource} to target ${target} at destination ${destination}...")
|
||||
target_sources(${target} PRIVATE "${resource}")
|
||||
set_property(SOURCE "${resource}" PROPERTY MACOSX_PACKAGE_LOCATION Resources)
|
||||
source_group("Resources" FILES "${resource}")
|
||||
endfunction()
|
||||
26
cmake/macos/resources/ccache-launcher-c.in
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [[ "$1" == "${CMAKE_C_COMPILER}" ]] ; then
|
||||
shift
|
||||
fi
|
||||
|
||||
export CCACHE_DIR='${CMAKE_SOURCE_DIR}/.ccache'
|
||||
export CCACHE_MAXSIZE='1G'
|
||||
export CCACHE_CPP2=true
|
||||
export CCACHE_DEPEND=true
|
||||
export CCACHE_DIRECT=true
|
||||
export CCACHE_FILECLONE=true
|
||||
export CCACHE_INODECACHE=true
|
||||
export CCACHE_COMPILERCHECK='content'
|
||||
|
||||
CCACHE_SLOPPINESS='file_stat_matches,include_file_mtime,include_file_ctime,system_headers'
|
||||
|
||||
if [[ "${CMAKE_C_COMPILER_ID}" == "AppleClang" ]]; then
|
||||
CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS},modules,clang_index_store"
|
||||
fi
|
||||
export CCACHE_SLOPPINESS
|
||||
|
||||
if [[ "${CI}" ]]; then
|
||||
export CCACHE_NOHASHDIR=true
|
||||
fi
|
||||
exec "${CMAKE_C_COMPILER_LAUNCHER}" "${CMAKE_C_COMPILER}" "$@"
|
||||
26
cmake/macos/resources/ccache-launcher-cxx.in
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#!/bin/sh
|
||||
|
||||
if [[ "$1" == "${CMAKE_CXX_COMPILER}" ]] ; then
|
||||
shift
|
||||
fi
|
||||
|
||||
export CCACHE_DIR='${CMAKE_SOURCE_DIR}/.ccache'
|
||||
export CCACHE_MAXSIZE='1G'
|
||||
export CCACHE_CPP2=true
|
||||
export CCACHE_DEPEND=true
|
||||
export CCACHE_DIRECT=true
|
||||
export CCACHE_FILECLONE=true
|
||||
export CCACHE_INODECACHE=true
|
||||
export CCACHE_COMPILERCHECK='content'
|
||||
|
||||
CCACHE_SLOPPINESS='file_stat_matches,include_file_mtime,include_file_ctime,system_headers'
|
||||
|
||||
if [[ "${CMAKE_C_COMPILER_ID}" == "AppleClang" ]]; then
|
||||
CCACHE_SLOPPINESS="${CCACHE_SLOPPINESS},modules,clang_index_store"
|
||||
fi
|
||||
export CCACHE_SLOPPINESS
|
||||
|
||||
if [[ "${CI}" ]]; then
|
||||
export CCACHE_NOHASHDIR=true
|
||||
fi
|
||||
exec "${CMAKE_CXX_COMPILER_LAUNCHER}" "${CMAKE_CXX_COMPILER}" "$@"
|
||||
35
cmake/macos/resources/create-package.cmake.in
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
make_directory("$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package/Library/Application Support/obs-studio/plugins")
|
||||
|
||||
if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin" AND NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.plugin")
|
||||
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" USE_SOURCE_PERMISSIONS)
|
||||
|
||||
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])$")
|
||||
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")
|
||||
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)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
make_directory("$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp")
|
||||
|
||||
execute_process(
|
||||
COMMAND /usr/bin/pkgbuild
|
||||
--identifier '@MACOS_BUNDLEID@'
|
||||
--version '@CMAKE_PROJECT_VERSION@'
|
||||
--root "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package"
|
||||
"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp/@CMAKE_PROJECT_NAME@.pkg"
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
|
||||
execute_process(
|
||||
COMMAND /usr/bin/productbuild
|
||||
--distribution "@CMAKE_CURRENT_BINARY_DIR@/distribution"
|
||||
--package-path "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp"
|
||||
"$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.pkg"
|
||||
COMMAND_ERROR_IS_FATAL ANY)
|
||||
|
||||
if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/@CMAKE_PROJECT_NAME@.pkg")
|
||||
file(REMOVE_RECURSE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/temp")
|
||||
file(REMOVE_RECURSE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/package")
|
||||
endif()
|
||||
33
cmake/macos/resources/distribution.in
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<installer-gui-script minSpecVersion="1.0">
|
||||
<options
|
||||
rootVolumeOnly="true"
|
||||
hostArchitectures="arm64,x86_64"
|
||||
customize="never"
|
||||
allow-external-scripts="no" />
|
||||
<domains enable_currentUserHome="true" enable_anywhere="false" enable_localSystem="false" />
|
||||
<title>@CMAKE_PROJECT_NAME@</title>
|
||||
<choices-outline>
|
||||
<line choice="obs-plugin" />
|
||||
</choices-outline>
|
||||
<choice id="obs-plugin" title="@CMAKE_PROJECT_NAME@" description="">
|
||||
<pkg-ref id="@MACOS_BUNDLEID@" />
|
||||
</choice>
|
||||
<pkg-ref id="@MACOS_BUNDLEID@" version="@CMAKE_PROJECT_VERSION@">#@CMAKE_PROJECT_NAME@.pkg</pkg-ref>
|
||||
<installation-check script="installCheck();" />
|
||||
<script>
|
||||
function installCheck() {
|
||||
var macOSVersion = system.version.ProductVersion
|
||||
|
||||
if (system.compareVersions(macOSVersion, '@CMAKE_OSX_DEPLOYMENT_TARGET@') == -1) {
|
||||
my.result.title = system.localizedStandardStringWithFormat(
|
||||
'InstallationCheckError',
|
||||
system.localizedString('DISTRIBUTION_TITLE')
|
||||
);
|
||||
my.result.message = ' ';
|
||||
my.result.type = 'Fatal';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</installer-gui-script>
|
||||
920
cmake/macos/resources/installer-macos.pkgproj.in
Normal file
|
|
@ -0,0 +1,920 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PACKAGES</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>MUST-CLOSE-APPLICATION-ITEMS</key>
|
||||
<array/>
|
||||
<key>MUST-CLOSE-APPLICATIONS</key>
|
||||
<false/>
|
||||
<key>PACKAGE_FILES</key>
|
||||
<dict>
|
||||
<key>DEFAULT_INSTALL_LOCATION</key>
|
||||
<string>/</string>
|
||||
<key>HIERARCHY</key>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Applications</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>BUNDLE_CAN_DOWNGRADE</key>
|
||||
<false/>
|
||||
<key>BUNDLE_POSTINSTALL_PATH</key>
|
||||
<dict>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>BUNDLE_PREINSTALL_PATH</key>
|
||||
<dict>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>../release/@CMAKE_INSTALL_CONFIG_NAME@/@CMAKE_PROJECT_NAME@.plugin</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>3</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>plugins</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>obs-studio</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>509</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>2</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Application Support</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Automator</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Documentation</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Extensions</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Filesystems</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Frameworks</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Input Methods</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Internet Plug-Ins</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>LaunchAgents</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>LaunchDaemons</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>PreferencePanes</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Preferences</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Printers</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>PrivilegedHelperTools</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>1005</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>QuickLook</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>QuickTime</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Screen Savers</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Scripts</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Services</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Widgets</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Library</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>Shared</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>1023</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>80</integer>
|
||||
<key>PATH</key>
|
||||
<string>Users</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>GID</key>
|
||||
<integer>0</integer>
|
||||
<key>PATH</key>
|
||||
<string>/</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PERMISSIONS</key>
|
||||
<integer>493</integer>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>UID</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>PAYLOAD_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>PRESERVE_EXTENDED_ATTRIBUTES</key>
|
||||
<false/>
|
||||
<key>SHOW_INVISIBLE</key>
|
||||
<false/>
|
||||
<key>SPLIT_FORKS</key>
|
||||
<true/>
|
||||
<key>TREAT_MISSING_FILES_AS_WARNING</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
<key>PACKAGE_SCRIPTS</key>
|
||||
<dict>
|
||||
<key>POSTINSTALL_PATH</key>
|
||||
<dict>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>PREINSTALL_PATH</key>
|
||||
<dict>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>RESOURCES</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>PACKAGE_SETTINGS</key>
|
||||
<dict>
|
||||
<key>AUTHENTICATION</key>
|
||||
<integer>0</integer>
|
||||
<key>CONCLUSION_ACTION</key>
|
||||
<integer>0</integer>
|
||||
<key>FOLLOW_SYMBOLIC_LINKS</key>
|
||||
<false/>
|
||||
<key>IDENTIFIER</key>
|
||||
<string>@MACOS_BUNDLEID@</string>
|
||||
<key>LOCATION</key>
|
||||
<integer>0</integer>
|
||||
<key>NAME</key>
|
||||
<string>@CMAKE_PROJECT_NAME@</string>
|
||||
<key>OVERWRITE_PERMISSIONS</key>
|
||||
<false/>
|
||||
<key>PAYLOAD_SIZE</key>
|
||||
<integer>-1</integer>
|
||||
<key>REFERENCE_PATH</key>
|
||||
<string></string>
|
||||
<key>RELOCATABLE</key>
|
||||
<false/>
|
||||
<key>USE_HFS+_COMPRESSION</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>@CMAKE_PROJECT_VERSION@</string>
|
||||
</dict>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>UUID</key>
|
||||
<string>@UUID_PACKAGE@</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROJECT</key>
|
||||
<dict>
|
||||
<key>PROJECT_COMMENTS</key>
|
||||
<dict>
|
||||
<key>NOTES</key>
|
||||
<data>
|
||||
</data>
|
||||
</dict>
|
||||
<key>PROJECT_PRESENTATION</key>
|
||||
<dict>
|
||||
<key>BACKGROUND</key>
|
||||
<dict>
|
||||
<key>APPAREANCES</key>
|
||||
<dict>
|
||||
<key>DARK_AQUA</key>
|
||||
<dict/>
|
||||
<key>LIGHT_AQUA</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
<key>SHARED_SETTINGS_FOR_ALL_APPAREANCES</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>INSTALLATION TYPE</key>
|
||||
<dict>
|
||||
<key>HIERARCHIES</key>
|
||||
<dict>
|
||||
<key>INSTALLER</key>
|
||||
<dict>
|
||||
<key>LIST</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CHILDREN</key>
|
||||
<array/>
|
||||
<key>DESCRIPTION</key>
|
||||
<array/>
|
||||
<key>OPTIONS</key>
|
||||
<dict>
|
||||
<key>HIDDEN</key>
|
||||
<false/>
|
||||
<key>STATE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>PACKAGE_UUID</key>
|
||||
<string>@UUID_PACKAGE@</string>
|
||||
<key>TITLE</key>
|
||||
<array/>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>UUID</key>
|
||||
<string>@UUID_INSTALLER@</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>REMOVED</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>MODE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>INSTALLATION_STEPS</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewIntroductionController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>Introduction</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewReadMeController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>ReadMe</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewLicenseController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>License</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewDestinationSelectController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>TargetSelect</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewInstallationTypeController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>PackageSelection</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewInstallationController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>Install</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS</key>
|
||||
<string>ICPresentationViewSummaryController</string>
|
||||
<key>INSTALLER_PLUGIN</key>
|
||||
<string>Summary</string>
|
||||
<key>LIST_TITLE_KEY</key>
|
||||
<string>InstallerSectionTitle</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>INTRODUCTION</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>LICENSE</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
<key>MODE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>README</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>SUMMARY</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
<key>TITLE</key>
|
||||
<dict>
|
||||
<key>LOCALIZATIONS</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>PROJECT_REQUIREMENTS</key>
|
||||
<dict>
|
||||
<key>LIST</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>BEHAVIOR</key>
|
||||
<integer>3</integer>
|
||||
<key>DICTIONARY</key>
|
||||
<dict>
|
||||
<key>IC_REQUIREMENT_OS_DISK_TYPE</key>
|
||||
<integer>1</integer>
|
||||
<key>IC_REQUIREMENT_OS_DISTRIBUTION_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>IC_REQUIREMENT_OS_MINIMUM_VERSION</key>
|
||||
<integer>101300</integer>
|
||||
</dict>
|
||||
<key>IC_REQUIREMENT_CHECK_TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>IDENTIFIER</key>
|
||||
<string>fr.whitebox.Packages.requirement.os</string>
|
||||
<key>MESSAGE</key>
|
||||
<array/>
|
||||
<key>NAME</key>
|
||||
<string>Operating System</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>RESOURCES</key>
|
||||
<array/>
|
||||
<key>ROOT_VOLUME_ONLY</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>PROJECT_SETTINGS</key>
|
||||
<dict>
|
||||
<key>ADVANCED_OPTIONS</key>
|
||||
<dict>
|
||||
<key>installer-script.domains:enable_currentUserHome</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>BUILD_FORMAT</key>
|
||||
<integer>0</integer>
|
||||
<key>BUILD_PATH</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>.</string>
|
||||
<key>PATH_TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>EXCLUDED_FILES</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.DS_Store</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove .DS_Store files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove ".DS_Store" files created by the Finder.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.pbdevelopment</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove .pbdevelopment files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove ".pbdevelopment" files created by ProjectBuilder or Xcode.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>CVS</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.cvsignore</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.cvspass</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.svn</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.git</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>.gitignore</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove SCM metadata</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>classes.nib</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>designable.db</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>info.nib</string>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Optimize nib files</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>PATTERNS_ARRAY</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>REGULAR_EXPRESSION</key>
|
||||
<false/>
|
||||
<key>STRING</key>
|
||||
<string>Resources Disabled</string>
|
||||
<key>TYPE</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</array>
|
||||
<key>PROTECTED</key>
|
||||
<true/>
|
||||
<key>PROXY_NAME</key>
|
||||
<string>Remove Resources Disabled folders</string>
|
||||
<key>PROXY_TOOLTIP</key>
|
||||
<string>Remove "Resources Disabled" folders.</string>
|
||||
<key>STATE</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>SEPARATOR</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NAME</key>
|
||||
<string>@CMAKE_PROJECT_NAME@</string>
|
||||
<key>PAYLOAD_ONLY</key>
|
||||
<false/>
|
||||
<key>TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>TYPE</key>
|
||||
<integer>0</integer>
|
||||
<key>VERSION</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
174
cmake/macos/xcode.cmake
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# CMake macOS Xcode module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
set(CMAKE_XCODE_GENERATE_SCHEME TRUE)
|
||||
|
||||
# Use a compiler wrapper to enable ccache in Xcode projects
|
||||
if(ENABLE_CCACHE AND CCACHE_PROGRAM)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/resources/ccache-launcher-c.in" ccache-launcher-c)
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/resources/ccache-launcher-cxx.in" ccache-launcher-cxx)
|
||||
|
||||
execute_process(
|
||||
COMMAND chmod a+rx "${CMAKE_CURRENT_BINARY_DIR}/ccache-launcher-c" "${CMAKE_CURRENT_BINARY_DIR}/ccache-launcher-cxx"
|
||||
)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CC "${CMAKE_CURRENT_BINARY_DIR}/ccache-launcher-c")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CXX "${CMAKE_CURRENT_BINARY_DIR}/ccache-launcher-cxx")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_LD "${CMAKE_C_COMPILER}")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_CXX_COMPILER}")
|
||||
endif()
|
||||
|
||||
# Set project variables
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION ${PLUGIN_BUILD_NUMBER})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DYLIB_COMPATIBILITY_VERSION 1.0.0)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${PLUGIN_VERSION})
|
||||
|
||||
# Set deployment target
|
||||
set(CMAKE_XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET ${CMAKE_OSX_DEPLOYMENT_TARGET})
|
||||
|
||||
if(NOT CODESIGN_TEAM)
|
||||
# Switch to manual codesigning if no codesigning team is provided
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${CODESIGN_IDENTITY}")
|
||||
else()
|
||||
if(CODESIGN_IDENTITY AND NOT CODESIGN_IDENTITY STREQUAL "-")
|
||||
# Switch to manual codesigning if a non-adhoc codesigning identity is provided
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "${CODESIGN_IDENTITY}")
|
||||
else()
|
||||
# Switch to automatic codesigning via valid team ID
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Development")
|
||||
endif()
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${CODESIGN_TEAM}")
|
||||
endif()
|
||||
|
||||
# Only create a single Xcode project file
|
||||
set(CMAKE_XCODE_GENERATE_TOP_LEVEL_PROJECT_ONLY TRUE)
|
||||
# Add all libraries to project link phase (lets Xcode handle linking)
|
||||
set(CMAKE_XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION)
|
||||
|
||||
# Enable codesigning with secure timestamp when not in Debug configuration (required for Notarization)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS[variant=Release] "--timestamp")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS[variant=RelWithDebInfo] "--timestamp")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS[variant=MinSizeRel] "--timestamp")
|
||||
|
||||
# Enable codesigning with hardened runtime option when not in Debug configuration (required for Notarization)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME[variant=Release] YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME[variant=RelWithDebInfo] YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME[variant=MinSizeRel] YES)
|
||||
|
||||
# Disable injection of Xcode's base entitlements used for debugging when not in Debug configuration (required for
|
||||
# Notarization)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=Release] NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=RelWithDebInfo] NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS[variant=MinSizeRel] NO)
|
||||
|
||||
# Use Swift version 5.0 by default
|
||||
set(CMAKE_XCODE_ATTRIBUTE_SWIFT_VERSION 5.0)
|
||||
|
||||
# Use DWARF with separate dSYM files when in Release or MinSizeRel configuration.
|
||||
#
|
||||
# * Currently overruled by CMake's Xcode generator, requires adding '-g' flag to raw compiler command line for desired
|
||||
# output configuration. Report to KitWare.
|
||||
#
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Debug] dwarf)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=RelWithDebInfo] dwarf)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=Release] dwarf-with-dsym)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT[variant=MinSizeRel] dwarf-with-dsym)
|
||||
|
||||
# Make all symbols hidden by default (currently overriden by CMake's compiler flags)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_SYMBOLS_PRIVATE_EXTERN YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_INLINES_ARE_PRIVATE_EXTERN YES)
|
||||
|
||||
# Strip unused code
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEAD_CODE_STRIPPING YES)
|
||||
|
||||
# Build active architecture only in Debug configuration
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
|
||||
|
||||
# Enable testability in Debug configuration
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_TESTABILITY[variant=Debug] YES)
|
||||
|
||||
# Enable using ARC in ObjC by default
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
|
||||
# Enable weak references in manual retain release
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES)
|
||||
# Disable strict aliasing
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_STRICT_ALIASING NO)
|
||||
|
||||
# Set C++ language default to c17
|
||||
#
|
||||
# * CMake explicitly sets the version via compiler flag when transitive dependencies require specific compiler feature
|
||||
# set, resulting in the flag being added twice. Report to KitWare as a feature request for Xcode generator
|
||||
# * See also: https://gitlab.kitware.com/cmake/cmake/-/issues/17183
|
||||
#
|
||||
# set(CMAKE_XCODE_ATTRIBUTE_GCC_C_LANGUAGE_STANDARD c17)
|
||||
#
|
||||
# Set C++ language default to c++17
|
||||
#
|
||||
# * See above. Report to KitWare as a feature request for Xcode generator
|
||||
#
|
||||
# set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD c++17)
|
||||
|
||||
# Enable support for module imports in ObjC
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES YES)
|
||||
# Enable automatic linking of imported modules in ObjC
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_MODULES_AUTOLINK YES)
|
||||
# Enable strict msg_send rules for ObjC
|
||||
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_STRICT_OBJC_MSGSEND YES)
|
||||
|
||||
# Set default warnings for ObjC and C++
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING YES_ERROR)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_BOOL_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_COMMA YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_CONSTANT_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_EMPTY_BODY YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_ENUM_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_INFINITE_RECURSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_INT_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_NON_LITERAL_NULL_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_OBJC_LITERAL_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_RANGE_LOOP_ANALYSIS YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_STRICT_PROTOTYPES NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_SUSPICIOUS_MOVE YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN_UNREACHABLE_CODE YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CLANG_WARN__DUPLICATE_METHOD_MATCH YES)
|
||||
|
||||
# Set default warnings for C and C++
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_NO_COMMON_BLOCKS YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_64_TO_32_BIT_CONVERSION YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_ABOUT_MISSING_NEWLINE YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_ABOUT_RETURN_TYPE YES_ERROR)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_CHECK_SWITCH_STATEMENTS YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_FOUR_CHARACTER_CONSTANTS YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_SHADOW NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_SIGN_COMPARE YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_TYPECHECK_CALLS_TO_PRINTF YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNDECLARED_SELECTOR YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNINITIALIZED_AUTOS YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNUSED_FUNCTION NO)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNUSED_PARAMETER YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNUSED_VALUE YES)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_WARN_UNUSED_VARIABLE YES)
|
||||
|
||||
# Add additional warning compiler flags
|
||||
set(CMAKE_XCODE_ATTRIBUTE_WARNING_CFLAGS "-Wvla -Wformat-security")
|
||||
|
||||
if(CMAKE_COMPILE_WARNING_AS_ERROR)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GCC_TREAT_WARNINGS_AS_ERRORS YES)
|
||||
endif()
|
||||
|
||||
# Enable color diagnostics
|
||||
set(CMAKE_COLOR_DIAGNOSTICS TRUE)
|
||||
|
||||
# Disable usage of RPATH in build or install configurations
|
||||
set(CMAKE_SKIP_RPATH TRUE)
|
||||
# Have Xcode set default RPATH entries
|
||||
set(CMAKE_XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks")
|
||||
24
cmake/windows/buildspec.cmake
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# CMake Windows build dependencies module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(buildspec_common)
|
||||
|
||||
# _check_dependencies_windows: Set up Windows slice for _check_dependencies
|
||||
function(_check_dependencies_windows)
|
||||
set(arch ${CMAKE_VS_PLATFORM_NAME})
|
||||
set(platform windows-${arch})
|
||||
|
||||
set(dependencies_dir "${CMAKE_CURRENT_SOURCE_DIR}/.deps")
|
||||
set(prebuilt_filename "windows-deps-VERSION-ARCH-REVISION.zip")
|
||||
set(prebuilt_destination "obs-deps-VERSION-ARCH")
|
||||
set(qt6_filename "windows-deps-qt6-VERSION-ARCH-REVISION.zip")
|
||||
set(qt6_destination "obs-deps-qt6-VERSION-ARCH")
|
||||
set(obs-studio_filename "VERSION.zip")
|
||||
set(obs-studio_destination "obs-studio-VERSION")
|
||||
set(dependencies_list prebuilt qt6 obs-studio)
|
||||
|
||||
_check_dependencies()
|
||||
endfunction()
|
||||
|
||||
_check_dependencies_windows()
|
||||
63
cmake/windows/compilerconfig.cmake
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# CMake Windows compiler configuration module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(compiler_common)
|
||||
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase)
|
||||
|
||||
message(DEBUG "Current Windows API version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
|
||||
if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM)
|
||||
message(DEBUG "Maximum Windows API version: ${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION_MAXIMUM}")
|
||||
endif()
|
||||
|
||||
if(CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION VERSION_LESS 10.0.20348)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"OBS requires Windows 10 SDK version 10.0.20348.0 or more recent.\n"
|
||||
"Please download and install the most recent Windows platform SDK."
|
||||
)
|
||||
endif()
|
||||
|
||||
set(_obs_msvc_c_options /MP /Zc:__cplusplus /Zc:preprocessor)
|
||||
set(_obs_msvc_cpp_options /MP /Zc:__cplusplus /Zc:preprocessor)
|
||||
|
||||
if(CMAKE_CXX_STANDARD GREATER_EQUAL 20)
|
||||
list(APPEND _obs_msvc_cpp_options /Zc:char8_t-)
|
||||
endif()
|
||||
|
||||
add_compile_options(
|
||||
/W3
|
||||
/utf-8
|
||||
/Brepro
|
||||
/permissive-
|
||||
"$<$<COMPILE_LANG_AND_ID:C,MSVC>:${_obs_msvc_c_options}>"
|
||||
"$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:${_obs_msvc_cpp_options}>"
|
||||
"$<$<COMPILE_LANG_AND_ID:C,Clang>:${_obs_clang_c_options}>"
|
||||
"$<$<COMPILE_LANG_AND_ID:CXX,Clang>:${_obs_clang_cxx_options}>"
|
||||
$<$<NOT:$<CONFIG:Debug>>:/Gy>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/GL>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/Oi>
|
||||
)
|
||||
|
||||
add_compile_definitions(
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_CRT_NONSTDC_NO_WARNINGS
|
||||
$<$<CONFIG:DEBUG>:DEBUG>
|
||||
$<$<CONFIG:DEBUG>:_DEBUG>
|
||||
)
|
||||
|
||||
add_link_options(
|
||||
$<$<NOT:$<CONFIG:Debug>>:/OPT:REF>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/OPT:ICF>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/LTCG>
|
||||
$<$<NOT:$<CONFIG:Debug>>:/INCREMENTAL:NO>
|
||||
/DEBUG
|
||||
/Brepro
|
||||
)
|
||||
|
||||
if(CMAKE_COMPILE_WARNING_AS_ERROR)
|
||||
add_link_options(/WX)
|
||||
endif()
|
||||
18
cmake/windows/defaults.cmake
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# CMake Windows defaults module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
# Enable find_package targets to become globally available targets
|
||||
set(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL TRUE)
|
||||
|
||||
include(buildspec)
|
||||
|
||||
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
|
||||
set(
|
||||
CMAKE_INSTALL_PREFIX
|
||||
"$ENV{ALLUSERSPROFILE}/obs-studio/plugins"
|
||||
CACHE STRING
|
||||
"Default plugin installation directory"
|
||||
FORCE
|
||||
)
|
||||
endif()
|
||||
108
cmake/windows/helpers.cmake
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# CMake Windows helper functions module
|
||||
|
||||
include_guard(GLOBAL)
|
||||
|
||||
include(helpers_common)
|
||||
|
||||
# set_target_properties_plugin: Set target properties for use in obs-studio
|
||||
function(set_target_properties_plugin target)
|
||||
set(options "")
|
||||
set(oneValueArgs "")
|
||||
set(multiValueArgs PROPERTIES)
|
||||
cmake_parse_arguments(PARSE_ARGV 0 _STPO "${options}" "${oneValueArgs}" "${multiValueArgs}")
|
||||
|
||||
message(DEBUG "Setting additional properties for target ${target}...")
|
||||
|
||||
while(_STPO_PROPERTIES)
|
||||
list(POP_FRONT _STPO_PROPERTIES key value)
|
||||
set_property(TARGET ${target} PROPERTY ${key} "${value}")
|
||||
endwhile()
|
||||
|
||||
string(TIMESTAMP CURRENT_YEAR "%Y")
|
||||
|
||||
set_target_properties(${target} PROPERTIES VERSION 0 SOVERSION ${PLUGIN_VERSION})
|
||||
|
||||
install(TARGETS ${target} RUNTIME DESTINATION "${target}/bin/64bit" LIBRARY DESTINATION "${target}/bin/64bit")
|
||||
|
||||
install(
|
||||
FILES "$<TARGET_PDB_FILE:${target}>"
|
||||
CONFIGURATIONS RelWithDebInfo Debug Release
|
||||
DESTINATION "${target}/bin/64bit"
|
||||
OPTIONAL
|
||||
)
|
||||
|
||||
if(TARGET plugin-support)
|
||||
target_link_libraries(${target} PRIVATE plugin-support)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:${target}>"
|
||||
"$<$<CONFIG:Debug,RelWithDebInfo,Release>:$<TARGET_PDB_FILE:${target}>>"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>"
|
||||
COMMENT "Copy ${target} to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
target_install_resources(${target})
|
||||
|
||||
get_target_property(target_sources ${target} SOURCES)
|
||||
set(target_ui_files ${target_sources})
|
||||
list(FILTER target_ui_files INCLUDE REGEX ".+\\.(ui|qrc)")
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" PREFIX "UI Files" FILES ${target_ui_files})
|
||||
|
||||
configure_file(cmake/windows/resources/resource.rc.in "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.rc")
|
||||
target_sources(${CMAKE_PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.rc")
|
||||
endfunction()
|
||||
|
||||
# Helper function to add resources into bundle
|
||||
function(target_install_resources target)
|
||||
message(DEBUG "Installing resources for target ${target}...")
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data")
|
||||
file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*")
|
||||
foreach(data_file IN LISTS data_files)
|
||||
cmake_path(
|
||||
RELATIVE_PATH
|
||||
data_file
|
||||
BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/"
|
||||
OUTPUT_VARIABLE relative_path
|
||||
)
|
||||
cmake_path(GET relative_path PARENT_PATH relative_path)
|
||||
target_sources(${target} PRIVATE "${data_file}")
|
||||
source_group("Resources/${relative_path}" FILES "${data_file}")
|
||||
endforeach()
|
||||
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" DESTINATION "${target}/data" USE_SOURCE_PERMISSIONS)
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMAND
|
||||
"${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/data"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMENT "Copy ${target} resources to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Helper function to add a specific resource to a bundle
|
||||
function(target_add_resource target resource)
|
||||
message(DEBUG "Add resource '${resource}' to target ${target} at destination '${target_destination}'...")
|
||||
|
||||
install(FILES "${resource}" DESTINATION "${target}/data" COMPONENT Runtime)
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${target}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy "${resource}" "${CMAKE_CURRENT_BINARY_DIR}/rundir/$<CONFIG>/${target}"
|
||||
COMMENT "Copy ${target} resource ${resource} to rundir"
|
||||
VERBATIM
|
||||
)
|
||||
source_group("Resources" FILES "${resource}")
|
||||
endfunction()
|
||||
105
cmake/windows/resources/installer-Windows.iss.in
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#define MyAppName "@CMAKE_PROJECT_NAME@"
|
||||
#define MyAppVersion "@CMAKE_PROJECT_VERSION@"
|
||||
#define MyAppPublisher "@PLUGIN_AUTHOR@"
|
||||
#define MyAppURL "@PLUGIN_WEBSITE@"
|
||||
|
||||
[Setup]
|
||||
; NOTE: The value of AppId uniquely identifies this application.
|
||||
; Do not use the same AppId value in installers for other applications.
|
||||
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||
AppId={{@UUID_APP@}}
|
||||
AppName={#MyAppName}
|
||||
AppVersion={#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={code:GetDirName}
|
||||
AppendDefaultDirName=no
|
||||
DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename={#MyAppName}-{#MyAppVersion}-windows-installer
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
DirExistsWarning=no
|
||||
AllowNoIcons=yes
|
||||
; Wizard Information
|
||||
WizardStyle=modern
|
||||
WizardResizable=yes
|
||||
SetupIconFile="../media/icon.ico"
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\release\Package\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\LICENSE"; Flags: dontcopy
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||
|
||||
[Code]
|
||||
procedure InitializeWizard();
|
||||
var
|
||||
GPLText: AnsiString;
|
||||
Page: TOutputMsgMemoWizardPage;
|
||||
begin
|
||||
ExtractTemporaryFile('LICENSE');
|
||||
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
|
||||
Page := CreateOutputMsgMemoPage(wpWelcome,
|
||||
'License Information', 'Please review the license terms before installing {#MyAppName}',
|
||||
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
|
||||
String(GPLText)
|
||||
);
|
||||
end;
|
||||
|
||||
// credit where it's due :
|
||||
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||
function GetDirName(Value: string): string;
|
||||
var
|
||||
InstallPath: string;
|
||||
begin
|
||||
// initialize default path, which will be returned when the following registry
|
||||
// key queries fail due to missing keys or for some different reason
|
||||
Result := ExpandConstant('{pf}\obs-studio');
|
||||
// query the first registry value; if this succeeds, return the obtained value
|
||||
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||
Result := InstallPath;
|
||||
if RegQueryStringValue(HKLM64, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||
Result := InstallPath;
|
||||
end;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
function NextButtonClick(PageId: Integer): Boolean;
|
||||
var
|
||||
ObsFileName: string;
|
||||
ObsMS, ObsLS: Cardinal;
|
||||
ObsMajorVersion, ObsMinorVersion: Cardinal;
|
||||
begin
|
||||
Result := True;
|
||||
if not (PageId = wpSelectDir) then begin
|
||||
exit;
|
||||
end;
|
||||
ObsFileName := ExpandConstant('{app}\bin\64bit\obs64.exe');
|
||||
if not FileExists(ObsFileName) then begin
|
||||
MsgBox('OBS Studio (bin\64bit\obs64.exe) does not seem to be installed in that folder. Please select the correct folder.', mbError, MB_OK);
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
Result := GetVersionNumbers(ObsFileName, ObsMS, ObsLS);
|
||||
if not Result then begin
|
||||
MsgBox('Failed to read version from OBS Studio (bin\64bit\obs64.exe).', mbError, MB_OK);
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
{ shift 16 bits to the right to get major version }
|
||||
ObsMajorVersion := ObsMS shr 16;
|
||||
{ select only low 16 bits }
|
||||
ObsMinorVersion := ObsMS and $FFFF;
|
||||
if ObsMajorVersion < 31 then begin
|
||||
MsgBox('Version of OBS Studio (bin\64bit\obs64.exe) is lower than the version 31 required.', mbError, MB_OK);
|
||||
Result := False;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
32
cmake/windows/resources/resource.rc.in
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
1 VERSIONINFO
|
||||
FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0
|
||||
PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0
|
||||
FILEFLAGSMASK 0x0L
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x0L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "${PLUGIN_AUTHOR}"
|
||||
VALUE "FileDescription", "${PROJECT_NAME}"
|
||||
VALUE "FileVersion", "${PROJECT_VERSION}"
|
||||
VALUE "InternalName", "${PROJECT_NAME}"
|
||||
VALUE "LegalCopyright", "(C) ${CURRENT_YEAR} ${PLUGIN_AUTHOR}"
|
||||
VALUE "OriginalFilename", "${PROJECT_NAME}"
|
||||
VALUE "ProductName", "${PROJECT_NAME}"
|
||||
VALUE "ProductVersion", "${PROJECT_VERSION}"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
1878
config-dialog.cpp
Normal file
121
config-dialog.hpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QMainWindow>
|
||||
#include <qtextedit.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QGroupBox>
|
||||
#include <qlistwidget.h>
|
||||
#include <qspinbox.h>
|
||||
#include <QFormLayout>
|
||||
#include <QRadioButton>
|
||||
|
||||
#include "hotkey-edit.hpp"
|
||||
|
||||
class CanvasDock;
|
||||
|
||||
class OBSBasicSettings : public QDialog {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QIcon generalIcon READ GetGeneralIcon WRITE SetGeneralIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon appearanceIcon READ GetAppearanceIcon WRITE SetAppearanceIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon streamIcon READ GetStreamIcon WRITE SetStreamIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon outputIcon READ GetOutputIcon WRITE SetOutputIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon audioIcon READ GetAudioIcon WRITE SetAudioIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon videoIcon READ GetVideoIcon WRITE SetVideoIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon hotkeysIcon READ GetHotkeysIcon WRITE SetHotkeysIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon accessibilityIcon READ GetAccessibilityIcon WRITE SetAccessibilityIcon DESIGNABLE true)
|
||||
Q_PROPERTY(QIcon advancedIcon READ GetAdvancedIcon WRITE SetAdvancedIcon DESIGNABLE true)
|
||||
private:
|
||||
CanvasDock *canvasDock;
|
||||
QLabel *newVersion;
|
||||
QListWidget *listWidget;
|
||||
QComboBox *resolution;
|
||||
QSpinBox *streamingVideoBitrate;
|
||||
QCheckBox *streamingMatchMain;
|
||||
QSpinBox *recordVideoBitrate;
|
||||
QCheckBox *recordingMatchMain;
|
||||
QComboBox *audioBitrate;
|
||||
QComboBox *virtualCameraMode;
|
||||
QCheckBox *backtrackClip;
|
||||
QSpinBox *backtrackDuration;
|
||||
QLineEdit *backtrackPath;
|
||||
QCheckBox *maxSizeEnable;
|
||||
QSpinBox *maxSize;
|
||||
QCheckBox *maxTimeEnable;
|
||||
QSpinBox *maxTime;
|
||||
QLabel *multitrackLabel;
|
||||
|
||||
QFormLayout *streamingLayout;
|
||||
std::vector<QLineEdit *> server_names;
|
||||
std::vector<QComboBox *> servers;
|
||||
std::vector<QLineEdit *> keys;
|
||||
std::vector<QCheckBox *> servers_enabled;
|
||||
|
||||
QCheckBox *streamDelayEnable;
|
||||
QSpinBox *streamDelayDuration;
|
||||
QCheckBox *streamDelayPreserve;
|
||||
|
||||
QCheckBox *streamingUseMain;
|
||||
std::vector<QRadioButton *> streamingAudioTracks;
|
||||
QComboBox *streamingEncoder;
|
||||
obs_properties_t *stream_encoder_properties = nullptr;
|
||||
obs_data_t *stream_encoder_settings = nullptr;
|
||||
std::map<obs_property_t *, QWidget *> stream_encoder_property_widgets;
|
||||
|
||||
QLineEdit *recordPath;
|
||||
QLineEdit *recordFileFormat;
|
||||
|
||||
QCheckBox *recordingUseMain;
|
||||
QLineEdit *filenameFormat;
|
||||
QComboBox *fileFormat;
|
||||
std::vector<QCheckBox *> recordingAudioTracks;
|
||||
QComboBox *recordingEncoder;
|
||||
obs_properties_t *record_encoder_properties = nullptr;
|
||||
obs_data_t *record_encoder_settings = nullptr;
|
||||
std::map<obs_property_t *, QWidget *> record_encoder_property_widgets;
|
||||
|
||||
std::vector<OBSHotkeyWidget *> hotkeys;
|
||||
|
||||
QIcon GetGeneralIcon() const;
|
||||
QIcon GetAppearanceIcon() const;
|
||||
QIcon GetStreamIcon() const;
|
||||
QIcon GetOutputIcon() const;
|
||||
QIcon GetAudioIcon() const;
|
||||
QIcon GetVideoIcon() const;
|
||||
QIcon GetHotkeysIcon() const;
|
||||
QIcon GetAccessibilityIcon() const;
|
||||
QIcon GetAdvancedIcon() const;
|
||||
|
||||
std::vector<obs_hotkey_t *> GetHotKeysFromOutput(obs_output_t *obs_output);
|
||||
std::vector<obs_key_combination_t> GetCombosForHotkey(obs_hotkey_id hotkey);
|
||||
std::vector<obs_hotkey_t *> GetHotkeyById(obs_hotkey_id hotkey);
|
||||
obs_hotkey_t *GetHotkeyByName(QString name);
|
||||
|
||||
void SetEncoderBitrate(obs_encoder_t *obs_encoder, bool record);
|
||||
void AddProperty(obs_property_t *property, obs_data_t *settings, QFormLayout *layout,
|
||||
std::map<obs_property_t *, QWidget *> *widgets);
|
||||
void LoadProperty(obs_property_t *property, obs_data_t *settings, QWidget *widget);
|
||||
void RefreshProperties(std::map<obs_property_t *, QWidget *> *widgets, QFormLayout *layout);
|
||||
void AddServer();
|
||||
|
||||
private slots:
|
||||
void SetGeneralIcon(const QIcon &icon);
|
||||
void SetAppearanceIcon(const QIcon &icon);
|
||||
void SetStreamIcon(const QIcon &icon);
|
||||
void SetOutputIcon(const QIcon &icon);
|
||||
void SetAudioIcon(const QIcon &icon);
|
||||
void SetVideoIcon(const QIcon &icon);
|
||||
void SetHotkeysIcon(const QIcon &icon);
|
||||
void SetAccessibilityIcon(const QIcon &icon);
|
||||
void SetAdvancedIcon(const QIcon &icon);
|
||||
|
||||
public:
|
||||
OBSBasicSettings(CanvasDock *canvas_dock, QMainWindow *parent = nullptr);
|
||||
~OBSBasicSettings();
|
||||
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
public slots:
|
||||
};
|
||||
BIN
data/images/overflow.png
Normal file
|
After Width: | Height: | Size: 386 B |
72
data/locale/bn-BD.ini
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
Vertical="Vertical"
|
||||
VerticalCanvas="Vertical Canvas"
|
||||
Description="Vertical Canvas"
|
||||
Canvas="ক্যানভাস"
|
||||
VirtualCam="ভার্চুয়াল ক্যামেরা"
|
||||
LinkedScenes="লিঙ্ক করা Scenes"
|
||||
OnMainCanvas="প্রধান ক্যানভাসে দেখাও"
|
||||
SceneName="Scene নাম"
|
||||
SourceName="Source নাম"
|
||||
TransitionName="Transition নাম"
|
||||
Saving="Backtrack সংরক্ষণ করা হচ্ছে..."
|
||||
Saved="Backtrack সংরক্ষিত!"
|
||||
VerticalSettings="Vertical সেটিংস"
|
||||
General="General"
|
||||
Resolution="Resolution"
|
||||
ShowScenes="মূল scene list এ vertical scenes দেখাও"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack স্ট্রিমিং/রেকর্ডিংয়ের সময় চলে"
|
||||
BacktrackOn="Backtrack চালু"
|
||||
BacktrackOff="Backtrack বন্ধ"
|
||||
BacktrackDuration="Backtrack রেকর্ডিং দৈর্ঘ্য"
|
||||
BacktrackPath="Backtrack রেকর্ডিং পথ"
|
||||
StartBacktrackHotkey="Backtrack Hotkey শুরু করুন"
|
||||
StopBacktrackHotkey="Backtrack Hotkey বন্ধ করুন"
|
||||
SaveBacktrackHotkey="Backtrack Hotkey সংরক্ষণ করুন"
|
||||
Streaming="স্ট্রিমিং"
|
||||
ViewGuide="গাইড দেখুন"
|
||||
Name="নাম"
|
||||
Server="সার্ভার"
|
||||
Key="কী (Key)"
|
||||
Enabled="Enabled"
|
||||
Output="আউটপুট"
|
||||
StartStreamingHotkey="স্ট্রিমিং Hotkey শুরু করুন"
|
||||
StopStreamingHotkey="স্ট্রিমিং Hotkey বন্ধ করুন"
|
||||
Recording="রেকর্ডিং"
|
||||
StartRecordingHotkey="রেকর্ডিং Hotkey শুরু করুন"
|
||||
StopRecordingHotkey="রেকর্ডিং Hotkey বন্ধ করুন"
|
||||
Version="ভার্সন"
|
||||
MadeBy="♡ দ্বারা তৈরি"
|
||||
AddVerticalScene="vertical scene যুক্ত করুন"
|
||||
RemoveVerticalScene="vertical scene অপসারণ করুন"
|
||||
StreamVertical="সব vertical আউটপুটগুলো stream করুন"
|
||||
StreamVerticalMulti="পৃথক vertical আউটপুটগুলো নিয়ন্ত্রন করুন"
|
||||
RecordVertical="vertical রেকর্ড করুন"
|
||||
BacktrackClipVertical="Backtrack clip vertical"
|
||||
VirtualCameraVertical="Vertical ভার্চুয়াল ক্যামেরা"
|
||||
VerticalScene="Vertical Scene"
|
||||
tiktokError="আমরা লক্ষ্য করেছি যে আপনি টিকটকে স্ট্রিম করার চেষ্টা করছেন। দয়া করে নিশ্চিত করুন যে আপনার স্ট্রিম কি আপডেট করা আছে, কারণ টিকটক প্রতিটি স্ট্রিমের পরে এগুলো পরিবর্তন করে!"
|
||||
backtrackStartFail="Backtrack শুরু করা ব্যর্থ হয়েছে"
|
||||
backtrackCustomFfmpeg="backtrack শুরু করা যাচ্ছে না যখন রেকর্ডিং custom ffmpeg এ সেট করা আছে।"
|
||||
backtrackNoReplayBuffer="backtrack শুরু করা যাচ্ছে না কারণ কোনো রিপ্লে বাফার পাওয়া যায়নি।"
|
||||
UseMain="main OBS সেটিংস ব্যবহার করুন"
|
||||
NewVersion="নতুন ভার্সন (%1) <a href='https://aitum.tv/download/vertical/'>এখানে/a> পাওয়া যাবে"
|
||||
RecordPathError="কনফিগার করা Recording Path খালি রয়েছে। আপনার Recording Path টি Vertical Settings → Recording এর অধীনে পরীক্ষা করুন।"
|
||||
Help="সাহায্য"
|
||||
HelpSupport="Aitum Discord সার্ভারে যোগ দিন"
|
||||
CopyFromMain="main থেকে copy করুন"
|
||||
VirtualCameraWarning="vertical ভার্চুয়াল ক্যামেরা চালু করার পর, OBS কিছু সেটিংস edit করতে বাধা দিতে পারে কারণ এটি মনে করতে পারে ক্যামেরাটি এখনও সক্রিয়, এমনকি এটি বন্ধ হওয়ার পরেও। সেটিংস আবার edit করতে OBS রিস্টার্ট করুন।"
|
||||
NoOutputServer="কোনো আউটপুট সার্ভার নেই"
|
||||
NoOutputServerWarning="কোনো সক্রিয় আউটপুট সার্ভার পাওয়া যায়নি। নিশ্চিত করুন যে vertical streaming সেটিংস এ একটি আউটপুট সক্রিয় করা আছে।"
|
||||
StartAll="সব শুরু করুন"
|
||||
StopAll="সব বন্ধ করুন"
|
||||
HelpIntro="সমস্যা সম্মুখীন হচ্ছেন? Vertical সম্পর্কে আরও জানতে চান? নীচে আমাদের সহায়ক রিসোর্সগুলি দেখুন।"
|
||||
HelpTroubleshooterButton="Vertical ট্রাবলশুটার ওয়েবসাইটে ভিজিট করুন"
|
||||
HelpGuideButton="Vertical গাইডস ওয়েবসাইটে ভিজিট করুন"
|
||||
HelpDiscordButton="Aitum Discord ভিজিট করুন"
|
||||
VirtualCameraModeVertical="Vertical"
|
||||
VirtualCameraModeMain="Main"
|
||||
VirtualCameraModeBoth="Both"
|
||||
StreamingMatchMain="main OBS স্ট্রিম শুরু ও শেষ হলে Vertical স্ট্রিমিংও শুরু এবং শেষ হবে"
|
||||
RecordingMatchMain="main OBS স্ট্রিম শুরু ও শেষ হলে Vertical স্ট্রিমিংও শুরু এবং শেষ হবে"
|
||||
OutputsMultistream="যেহেতু আপনি Aitum Multistream ব্যবহার করছেন, আপনাকে Vertical স্ট্রিমিং আউটপুটগুলি Aitum Multistream সেটিংস থেকে নিয়ন্ত্রণ করতে হবে।\nএখানে আউটপুট সেটিংস পরিবর্তন করা নিষ্ক্রিয় করা হয়েছে।"
|
||||
67
data/locale/de-DE.ini
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
Vertical="Vertikal"
|
||||
VerticalCanvas="Vertikale Leinwand"
|
||||
Description="Vertikale Leinwand"
|
||||
Canvas="Leinwand"
|
||||
VirtualCam="Virtuelle Kamera"
|
||||
LinkedScenes="Verlinkte Szenen"
|
||||
OnMainCanvas="Zeige auf Hauptleinwand"
|
||||
SceneName="Szenen Name"
|
||||
SourceName="Quellen Name"
|
||||
TransitionName="Übergang Name"
|
||||
Saving="Backtrack speichern..."
|
||||
Saved="Backtrack gespeichert!"
|
||||
VerticalSettings="Vertikale Einstellungen"
|
||||
General="Allgemein"
|
||||
Resolution="Auflösung"
|
||||
ShowScenes="Zeige vertikale Szenen in Hauptszenen Liste"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack läuft während des Streamings/Aufnahme"
|
||||
BacktrackAlwaysOn="Backtrack immer an"
|
||||
BacktrackDuration="Backtrack Aufnahmelänge"
|
||||
BacktrackPath="Backtrack Aufnahme Pfad"
|
||||
SaveBacktrackHotkey="Speichere Backtrack Hotkey"
|
||||
Streaming="Streaming"
|
||||
ViewGuide="Ratgeber anschauen"
|
||||
Name="Name"
|
||||
Server="Server"
|
||||
Key="Schlüssel"
|
||||
Enabled="Aktiviert"
|
||||
Output="Ausgabe"
|
||||
StartStreamingHotkey="Start Streaming Hotkey"
|
||||
StopStreamingHotkey="Stoppe Streaming Hotkey"
|
||||
Recording="Aufnahme"
|
||||
StartRecordingHotkey="Start Aufnahme Hotkey"
|
||||
StopRecordingHotkey="Stoppe Aufnahme Hotkey"
|
||||
Version="Version"
|
||||
MadeBy="made with ♡ by"
|
||||
AddVerticalScene="Füge vertikale Szene hinzu"
|
||||
RemoveVerticalScene="Entferne vertikale Szene"
|
||||
StreamVertical="Streame alle vertikalen Ausgaben"
|
||||
RecordVertical="Aufnahme Vertikal"
|
||||
BacktrackClipVertical="Backtrack Clip vertikal"
|
||||
VirtualCameraVertical="Virtuelle Kamera vertikal"
|
||||
VerticalScene="Vertikale Szene"
|
||||
tiktokError="Hinweis, wir haben festgestellt das Du versuchst auf TikTok zu streamen, stelle sicher das Dein StreamKey aktualisiert ist, da TikTok diesen nach jedem Stream rotiert!"
|
||||
backtrackStartFail="Backtrack starting fehlgeschlagen"
|
||||
backtrackCustomFfmpeg="Backtrack kann nicht gestartet werden, wenn custom ffmpeg Einstellungen vorhanden sind"
|
||||
backtrackNoReplayBuffer="Backtrack kann nicht gestartet werden, wenn kein Wiederholungsbuffer gefunden wurde"
|
||||
UseMain="Benutze OBS Haupteinstellungen"
|
||||
NewVersion="Neue Version (%1) verfügbar <a href='https://aitum.tv/download/vertical/'>hier</a>"
|
||||
RecordPathError="Der eingestellte Aufnahmepfad ist leer. Prüfe den Aufnahmepfad unter Vertikale Einstellungen → Aufnahme."
|
||||
Help="Hilfe"
|
||||
HelpSupport="Trete dem Aitum Discord Server bei"
|
||||
CopyFromMain="Kopiere von Main"
|
||||
VirtualCameraWarning="Nach dem starten der vertikalen Kamera ist es evtl. nicht möglich OBS Einstellungen zu ändern, weil OBS denkt es wäre noch aktiv nachdem es gestoppt wurde. OBS muss neugestartet werden um Einstellungen zu ändern."
|
||||
NoOutputServer="Kein Ausgabe Server"
|
||||
NoOutputServerWarning="Es wurde kein Ausgabe Server gefunden. Stelle sicher das eine Ausgabe in den vertikalen Streaming Einstellungen ausgewählt ist."
|
||||
StartAll="Starte alle"
|
||||
StopAll="Stoppe alle"
|
||||
HelpIntro="Ein Problem aufgetreten? Du möchtest mehr über Vertikal erfahren? Prüfe unsere Hilfsresourcen unten."
|
||||
HelpTroubleshooterButton="Besuche die Vertikal Ratgeber Webseite"
|
||||
HelpGuideButton="Besuche die Vertikal Hilfe Webseite"
|
||||
HelpDiscordButton="Besuche den Aitum Discord"
|
||||
VirtualCameraModeVertical="Vertikal"
|
||||
VirtualCameraModeMain="Haupt"
|
||||
VirtualCameraModeBoth="Beide"
|
||||
StreamingMatchMain="Starte und stoppe das Streaming wenn Haupt OBS Streaming startet oder stoppt"
|
||||
RecordingMatchMain="Starte und stoppe die Aufnahme wenn Haupt OBS Aufnahme startet oder stoppt"
|
||||
81
data/locale/en-US.ini
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
Vertical="Vertical"
|
||||
VerticalCanvas="Vertical Canvas"
|
||||
Description="Vertical Canvas"
|
||||
Canvas="Canvas"
|
||||
VirtualCam="Virtual camera"
|
||||
LinkedScenes="Linked Scenes"
|
||||
OnMainCanvas="Show on main canvas"
|
||||
SceneName="Scene Name"
|
||||
SourceName="Source Name"
|
||||
TransitionName="Transition Name"
|
||||
Saving="Backtrack saving..."
|
||||
Saved="Backtrack saved!"
|
||||
VerticalSettings="Vertical Settings"
|
||||
General="General"
|
||||
Resolution="Resolution"
|
||||
ShowScenes="Show vertical scenes in main scene list"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack runs while streaming/recording"
|
||||
BacktrackOn="Backtrack on"
|
||||
BacktrackOff="Backtrack off"
|
||||
BacktrackDuration="Backtrack Recording Length"
|
||||
BacktrackPath="Backtrack Recording Path"
|
||||
StartBacktrackHotkey="Start Backtrack Hotkey"
|
||||
StopBacktrackHotkey="Stop Backtrack Hotkey"
|
||||
SaveBacktrackHotkey="Save Backtrack Hotkey"
|
||||
Streaming="Streaming"
|
||||
ViewGuide="View Guide"
|
||||
Name="Name"
|
||||
Server="Server"
|
||||
Key="Key"
|
||||
Enabled="Enabled"
|
||||
Output="Output"
|
||||
StartStreamingHotkey="Start Streaming Hotkey"
|
||||
StopStreamingHotkey="Stop Streaming Hotkey"
|
||||
Recording="Recording"
|
||||
StartRecordingHotkey="Start Recording Hotkey"
|
||||
StopRecordingHotkey="Stop Recording Hotkey"
|
||||
Version="Version"
|
||||
MadeBy="made with ♡ by"
|
||||
AddVerticalScene="Add vertical scene"
|
||||
RemoveVerticalScene="Remove vertical scene"
|
||||
StreamVertical="Stream all vertical outputs"
|
||||
StreamVerticalMulti="Control individual vertical outputs"
|
||||
RecordVertical="Record vertical"
|
||||
BacktrackClipVertical="Backtrack clip vertical"
|
||||
VirtualCameraVertical="Vertical virtual camera"
|
||||
VerticalScene="Vertical Scene"
|
||||
tiktokError="Additionally, as we've detected you are trying to stream to TikTok, please ensure your stream key is updated as TikTok cycles them after every stream!"
|
||||
backtrackStartFail="Backtrack starting failed"
|
||||
backtrackCustomFfmpeg="Can't start backtrack when recording is set to custom ffmpeg"
|
||||
backtrackNoReplayBuffer="Can't start backtrack when no replay buffer is found"
|
||||
UseMain="Use main OBS settings"
|
||||
NewVersion="New version (%1) available <a href='https://aitum.tv/download/vertical/'>here</a>"
|
||||
RecordPathError="The configured Recording Path is empty. Please check your Recording Path under Vertical Settings → Recording."
|
||||
Help="Help"
|
||||
HelpSupport="Join the Aitum Discord Server"
|
||||
CopyFromMain="Copy from main"
|
||||
VirtualCameraWarning="After starting the vertical virtual camera you might not be able to edit some OBS settings because it thinks it is still active after it stopped. Restart OBS to be able edit the settings again."
|
||||
NoOutputServer="No output server"
|
||||
NoOutputServerWarning="There is no enabled output server found. Make sure you have a output enabled in the vertical streaming settings."
|
||||
StartAll="Start all"
|
||||
StopAll="Stop all"
|
||||
HelpIntro="Encountering an issue? Want to learn more about Vertical? Check out our help resources below."
|
||||
HelpTroubleshooterButton="Visit the Vertical Troubleshooter website"
|
||||
HelpGuideButton="Visit the Vertical Guides website"
|
||||
HelpDiscordButton="Visit the Aitum Discord"
|
||||
VirtualCameraModeVertical="Vertical"
|
||||
VirtualCameraModeMain="Main"
|
||||
VirtualCameraModeBoth="Both"
|
||||
StreamingMatchMain="Start and stop streaming when main OBS starts and stops streaming"
|
||||
RecordingMatchMain="Start and stop recording when main OBS starts and stops recording"
|
||||
OutputsMultistream="As you are using Aitum Multistream, you must control Vertical streaming outputs from the Aitum Multistream Settings.\nChanging Output settings is disabled here."
|
||||
VerticalDonate="Support Aitum"
|
||||
SupportButton="Support Aitum"
|
||||
SupportTitle="Supporting Aitum"
|
||||
SupportText="Aitum Vertical is provided free by the Aitum Team for <u>you, the streaming community</u>.<br /><br />Your support is essential in helping us to continue our journey of making useful tools that empower creators to make awesome content.<br /><br />If our tools have helped streamline your workflows, enabled your channel to grow or even inspired you to start crearting content, please consider supporting our team.<br /><br /><strong><a href='https://aitum.tv/support-us'>Support the Aitum Team</a></strong>"
|
||||
BacktrackEncoder="Backtrack quality is set in the recording settings"
|
||||
Multitrack="Multitrack video"
|
||||
MultitrackDisabled="In the OBS stream settings you have selected a service that does not support Multitrack video or you have Multitrack video disabled"
|
||||
MultitrackVerticalSelected="Aitum Vertical is selected as extra canvas for Multitrack video streaming"
|
||||
MultitrackVerticalNotSelected="In the OBS stream settings you have Multitrack video enabled, but you do not have Aitum Vertical selected as aditional canvas"
|
||||
77
data/locale/es-ES.ini
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
Vertical="Vertical"
|
||||
VerticalCanvas="Lienzo vertical"
|
||||
Description="Lienzo vertical"
|
||||
Canvas="Lienzo"
|
||||
VirtualCam="Cámara virtual"
|
||||
LinkedScenes="Escenas enlazadas"
|
||||
OnMainCanvas="Mostrar en el lienzo principal"
|
||||
SceneName="Nombre de la escena"
|
||||
SourceName="Nombre de la fuente"
|
||||
TransitionName="Nombre de la transición"
|
||||
Saving="Guardando backtrack..."
|
||||
Saved="¡Backtrack guardado!"
|
||||
VerticalSettings="Ajustes verticales"
|
||||
General="General"
|
||||
Resolution="Resolución"
|
||||
ShowScenes="Mostrar escenas verticales en la lista de escenas principal"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack se ejecuta durante la transmisión/grabación"
|
||||
BacktrackOn="Backtrack activado"
|
||||
BacktrackOff="Backtrack desactivado"
|
||||
BacktrackDuration="Duración de la grabación"
|
||||
BacktrackPath="Ruta de la grabación"
|
||||
StartBacktrackHotkey="Hotkey para iniciar Backtrack"
|
||||
StopBacktrackHotkey="Hotkey para detener Backtrack"
|
||||
SaveBacktrackHotkey="Hotkey de guardado"
|
||||
Streaming="Emisión"
|
||||
ViewGuide="Ver guía"
|
||||
Name="Nombre"
|
||||
Server="Servidor"
|
||||
Key="Clave"
|
||||
Enabled="Habilitada"
|
||||
Output="Salida"
|
||||
StartStreamingHotkey="Hotkey para iniciar emisión"
|
||||
StopStreamingHotkey="Hotkey para detener emisión"
|
||||
Recording="Grabación"
|
||||
StartRecordingHotkey="Hotkey para iniciar transmisión"
|
||||
StopRecordingHotkey="Hotkey para detener transmisión"
|
||||
Version="Versión"
|
||||
MadeBy="hecha con ♡ por"
|
||||
AddVerticalScene="Agregar escena vertical"
|
||||
RemoveVerticalScene="Eliminar escena vertical"
|
||||
StreamVertical="Transmisión vertical"
|
||||
StreamVerticalMulti="Control de salidas verticales individuales"
|
||||
RecordVertical="Grabación vertical"
|
||||
BacktrackClipVertical="Backtrack clip vertical"
|
||||
VirtualCameraVertical="Cámara virtual vertical"
|
||||
VerticalScene="Escena vertical"
|
||||
tiktokError="Además, como hemos detectado que estás intentando transmitir a TikTok, asegúrate de que tu clave de transmisión esté actualizada ya que TikTok la cambia después de cada transmisión."
|
||||
backtrackStartFail="Error al iniciar el backtrack"
|
||||
backtrackCustomFfmpeg="No se puede iniciar el backtrack cuando la grabación está configurada como ffmpeg personalizado"
|
||||
backtrackNoReplayBuffer="No se puede iniciar un backtrack cuando no está activo el búfer de repetición"
|
||||
UseMain="Usar la configuración principal de OBS"
|
||||
NewVersion="Nueva version (%1) disponible <a href='https://aitum.tv/download/vertical/'>aquí</a>"
|
||||
RecordPathError="La ruta de grabación configurada está vacía. Verifica tu ruta de grabación en Ajustes verticales → Grabación."
|
||||
Help="Ayuda"
|
||||
HelpSupport="Únete al servidor de Discord de Aitum"
|
||||
CopyFromMain="Copiar de las transiciones principales"
|
||||
VirtualCameraWarning="Después de iniciar la cámara virtual vertical es posible que no puedas editar algunos ajustes de OBS porque él cree que todavía está activa después de detenerse. Reinicia OBS para poder editar los ajustes de nuevo."
|
||||
NoOutputServer="No hay servidor de salida"
|
||||
NoOutputServerWarning="No se ha encontrado ningún servidor de salida habilitado. Asegúrese de que tiene una salida habilitada en la configuración de transmisión vertical."
|
||||
StartAll="Iniciar todas"
|
||||
StopAll="Detener todas"
|
||||
HelpIntro="¿Encontraste algún problema? ¿Quieres saber más sobre Vertical? Consulta nuestros artículos de ayuda."
|
||||
HelpTroubleshooterButton="Visita la página de resolución de problemas de Vertical"
|
||||
HelpGuideButton="Visita la página de guías de Vertical"
|
||||
HelpDiscordButton="Visita el Discord de Aitum"
|
||||
VirtualCameraModeVertical="Vertical"
|
||||
VirtualCameraModeMain="Principal"
|
||||
VirtualCameraModeBoth="Ambas"
|
||||
StreamingMatchMain="Iniciar y detener la transmisión cuando OBS inicia y detiene la transmisión"
|
||||
RecordingMatchMain="Iniciar y detener la grabación cuando OBS inicia y detiene la grabación"
|
||||
OutputsMultistream="Al estar usando Aitum Multistream, debes controlar las salidas de transmisión verticales desde los ajustes de Aitum Multistream.\nCambiar las opciones de salida está deshabilitado aquí."
|
||||
VerticalDonate="Apoya a Aitum"
|
||||
SupportButton="Apoya a Aitum"
|
||||
SupportTitle="Apoyando a Aitum"
|
||||
SupportText="Aitum Vertical se proporciona de forma gratuita por el equipo de Aitum para <u>vosotros, la comunidad de streaming</u>.<br /><br />Vuestro apoyo es esencial para ayudarnos a continuar nuestro viaje de crear herramientas útiles que potencien a los creadores a realizar contenidos increíbles.<br /><br />Si nuestras herramientas te han ayudado a optimizar tu flujo de trabajo, han permitido que tu canal crezca o incluso te han inspirado a comenzar a crear contenido, considera apoyar a nuestro equipo.<br /><br /><strong><a href='https://aitum.tv/support-us'>Apoya al equipo de Aitum</a></strong>"
|
||||
BacktrackEncoder="La calidad del Backtrack se establece en los ajustes de grabación"
|
||||
66
data/locale/nl-NL.ini
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
Vertical="Verticaal"
|
||||
VerticalCanvas="Verticale Canvas"
|
||||
Description="Verticale Canvas"
|
||||
Canvas="Canvas"
|
||||
VirtualCam="Virtuele camera"
|
||||
LinkedScenes="Gelinkte Scènes"
|
||||
OnMainCanvas="Laat zien op het hoofd canvas"
|
||||
SceneName="Scène Naam"
|
||||
SourceName="Bron Naam"
|
||||
TransitionName="Overgang Naam"
|
||||
Saving="Backtrack aan het opslaan..."
|
||||
Saved="Backtrack opgeslagen!"
|
||||
VerticalSettings="Verticale Instellingen"
|
||||
General="Algemeen"
|
||||
Resolution="Resolutie"
|
||||
ShowScenes="Laat verticale scènes zien in de hoofd scène lijst"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack staat aan tijdends het streamen en/of opnemen"
|
||||
BacktrackAlwaysOn="Backtrack staat altijd aan"
|
||||
BacktrackDuration="Backtrack Opname Lengte"
|
||||
BacktrackPath="Backtrack Opnamepad"
|
||||
SaveBacktrackHotkey="Backtrack Opslaan Sneltoets"
|
||||
Streaming="Streamen"
|
||||
ViewGuide="Bekijk Gids"
|
||||
Name="Naam"
|
||||
Server="Server"
|
||||
Key="Stream key"
|
||||
Enabled="Ingeschakeld"
|
||||
Output="Uitvoer"
|
||||
StartStreamingHotkey="Streamen Starten Sneltoets"
|
||||
StopStreamingHotkey="Streamen Stoppen Sneltoets"
|
||||
Recording="Opnemen"
|
||||
StartRecordingHotkey="Opname Starten Sneltoets"
|
||||
StopRecordingHotkey="Opname Stoppen Sneltoets"
|
||||
Version="Versie"
|
||||
MadeBy="gemaakt met ♡ door"
|
||||
AddVerticalScene="Voeg verticale scène toe"
|
||||
RemoveVerticalScene="Verwijder verticale scène"
|
||||
StreamVertical="Alle verticale stream uitvoeren starten"
|
||||
StreamVerticalMulti="Afzonderlijke verticale uitvoeren bedienen"
|
||||
RecordVertical="Verticaal opnemen starten"
|
||||
BacktrackClipVertical="Backtrack clip verticaal"
|
||||
VirtualCameraVertical="Verticale virtuele camera"
|
||||
VerticalScene="Verticale Scène"
|
||||
tiktokError="Verder hebben we gedetecteerd dat je naar TikTok probeert te streamen. Je moet ervoor zorgen dat je stream key is bijgewerkt, aangezien TikTok ze na elke stream verandered!"
|
||||
backtrackStartFail="Starten van Backtrack is gefaald"
|
||||
backtrackCustomFfmpeg="Backtrack kan niet gestart worden wanneer opname is ingesteld als 'Aangepaste Uitvoer (FFmpeg)'"
|
||||
backtrackNoReplayBuffer="Kan backtrack niet starten wanneer er geen replay buffer gevonden is"
|
||||
UseMain="Gebruik de hoofdinstellingen van OBS"
|
||||
NewVersion="Er is <a href='https://aitum.tv/download/vertical/'>hier</a> een nieuwe versie (%1) beschikbaar"
|
||||
RecordPathError="Het geconfigureerde bestandspad is leeg. Controleer alstublieft het bestandspad onder Verticale Instellingen → Opnemen"
|
||||
Help="Help"
|
||||
HelpSupport="Word lid van de Aitum Discord server"
|
||||
CopyFromMain="Kopieer van de de hoofd scène lijst"
|
||||
VirtualCameraWarning="Na het starten van de verticale virtuele camera is het mogelijk dat je sommige OBS instellingen niet meer kan aanpassen. Dit is omdat OBS denkt dat de virtuele camera nogsteeds actief is. Herstart OBS om alle instellingen weer aan te kunnen passen"
|
||||
NoOutputServer="Geen uitvoerserver"
|
||||
NoOutputServerWarning="Er is geen ingeschakelde uitvoerserver gevonden. Zorg ervoor dat u een uitvoer hebt ingeschakeld in de verticale streaming-instellingen."
|
||||
StartAll="Alles starten"
|
||||
StopAll="Alles stoppen"
|
||||
HelpIntro="Heeft u een probleem ondervonden? Wilt u meer weten over Verticaal? Bekijk onze hulpmiddelen hieronder."
|
||||
HelpTroubleshooterButton="Bezoek de probleemoplosser website voor Verticaal"
|
||||
HelpGuideButton="Bezoek de gids website voor Verticaal"
|
||||
HelpDiscordButton="Bezoek de Aitum Discord"
|
||||
VirtualCameraModeVertical="Verticaal"
|
||||
VirtualCameraModeMain="Hoofd"
|
||||
VirtualCameraModeBoth="Beide"
|
||||
49
data/locale/pl-PL.ini
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
Vertical="Pionowy"
|
||||
VerticalCanvas="Pionowy obszar roboczy"
|
||||
Description="Pionowy obszar roboczy"
|
||||
Canvas="Obszar roboczy"
|
||||
VirtualCam="Wirtualna kamera"
|
||||
LinkedScenes="Powiązane sceny"
|
||||
OnMainCanvas="Pokaż w głównym obszarze roboczym"
|
||||
SceneName="Nazwa sceny"
|
||||
SourceName="Nazwa źródła"
|
||||
TransitionName="Nazwa przejścia"
|
||||
Saving="Zapisuję Backtrack..."
|
||||
Saved="Backtrack zapisany!"
|
||||
VerticalSettings="Ustawienia pionu"
|
||||
General="Glówne"
|
||||
Resolution="Rozdzielczość"
|
||||
ShowScenes="Pokaż pionowe sceny na głównej liście scen"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack działa kiedy streamujesz/nagrywasz"
|
||||
BacktrackAlwaysOn="Backtrack zawsze włączony"
|
||||
BacktrackDuration="Długość nagrania Backtrack"
|
||||
BacktrackPath="Miejsce zapisu pliku Backtrack"
|
||||
SaveBacktrackHotkey="Skrót: zapisz Backtrack"
|
||||
Streaming="Streaming"
|
||||
Server="Serwer"
|
||||
Key="Klucz"
|
||||
StartStreamingHotkey="Skrót: rozpocznij stream"
|
||||
StopStreamingHotkey="Skrót: zatrzymaj stream"
|
||||
Recording="Nagrywanie"
|
||||
StartRecordingHotkey="Skrót: rozpocznij nagrywanie"
|
||||
StopRecordingHotkey="Skrót: zatrzymaj nagrywanie"
|
||||
Version="Wersja"
|
||||
MadeBy="stworzone z ♡ przez"
|
||||
AddVerticalScene="Dodaj pionową scenę"
|
||||
RemoveVerticalScene="Usuń pionową scenę"
|
||||
StreamVertical="Pionowy stream"
|
||||
RecordVertical="Pionowe nagranie"
|
||||
BacktrackClipVertical="Pionowy klip Backtrack"
|
||||
VirtualCameraVertical="Pionowa wirtualna kamera"
|
||||
VerticalScene="Pionowa scena"
|
||||
tiktokError="Additionally, as we've detected you are trying to stream to TikTok, please ensure your stream key is updated as TikTok cycles them after every stream!"
|
||||
backtrackStartFail="Rozpoczęcie Backtrack nieudane"
|
||||
backtrackCustomFfmpeg="Nie możesz włączyć Backtrack jeżeli nagrywanie jest ustawione na custom ffmpeg"
|
||||
backtrackNoReplayBuffer="Nie możesz włączyć Backtrack jeżeli nie znaleziono nagrywania powtórek."
|
||||
UseMain="Użyj ustawień OBS"
|
||||
NewVersion="Nowa wersja (%1) dostępna <a href='https://aitum.tv/download/vertical/'>tutaj</a>"
|
||||
RecordPathError="Skonfigurowana ścieżka dostępu do nagrań jest pusta. Sprawdź proszę swoją ścieżkę dostępu w Pionowe ustawienia → Nagrywanie."
|
||||
Help="Pomoc"
|
||||
HelpSupport="Dołącz do naszego serwera Discord: Aitum"
|
||||
CopyFromMain="Skopiuj z głównej"
|
||||
68
data/locale/pt-BR.ini
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
Vertical="Vertical"
|
||||
VerticalCanvas="Projetor Vertical"
|
||||
Description="Projetor Vertical"
|
||||
Canvas="Projetor"
|
||||
VirtualCam="Câmera Virtual"
|
||||
LinkedScenes="Cenas Vinculadas"
|
||||
OnMainCanvas="Mostrar na Projetor principal"
|
||||
SceneName="Nome da Cena"
|
||||
SourceName="Nome da Fonte"
|
||||
TransitionName="Nome da Transição"
|
||||
Saving="Salvando Backtrack..."
|
||||
Saved="Backtrack salvo!"
|
||||
VerticalSettings="Configurações Verticais"
|
||||
General="Geral"
|
||||
Resolution="Resolução"
|
||||
ShowScenes="Mostrar cenas verticais na lista principal de cenas"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="O Backtrack fica ativo enquanto você transmite/grava"
|
||||
BacktrackAlwaysOn="Backtrack sempre ativo"
|
||||
BacktrackDuration="Tamanho da Gravação do Backtrack"
|
||||
BacktrackPath="Local de Salvamento do Backtrack"
|
||||
SaveBacktrackHotkey="Botão de Salvamento do Backtrack"
|
||||
Streaming="Transmitindo"
|
||||
ViewGuide="Ver Guia"
|
||||
Name="Nome"
|
||||
Server="Servidor"
|
||||
Key="Chave"
|
||||
Enabled="Ativo"
|
||||
Output="Saída"
|
||||
StartStreamingHotkey="Botão de Início da Transmissão"
|
||||
StopStreamingHotkey="Botão de Término da Transmissão"
|
||||
Recording="Gravando"
|
||||
StartRecordingHotkey="Botão de Início da Gravação"
|
||||
StopRecordingHotkey="Botão de Término da Gravação"
|
||||
Version="Versão"
|
||||
MadeBy="feito com ♡ por"
|
||||
AddVerticalScene="Adicionar cena vertical"
|
||||
RemoveVerticalScene="Remover cena vertical"
|
||||
StreamVertical="Transmitir todas as saídas verticais"
|
||||
StreamVerticalMulti="Controlar saídas verticais individuais"
|
||||
RecordVertical="Gravação Vertical"
|
||||
BacktrackClipVertical="Criar clipe vertical"
|
||||
VirtualCameraVertical="Câmera virtual vertical"
|
||||
VerticalScene="Cena Vertical"
|
||||
tiktokError="Junto a isso, já que detectamos que você está tentando transmitir para o TikTok, por favor verifique se sua chanve de transmissão foi atualizada, já que o TikTok as muda após cada transmissão!"
|
||||
backtrackStartFail="Falha ao iniciar o Backtrack"
|
||||
backtrackCustomFfmpeg="Impossível iniciar o backtrack quando a gravação está configurada como custom ffmpeg"
|
||||
backtrackNoReplayBuffer="Impossível iniciar o backtrack quando nenhum replay buffer é encontrado"
|
||||
UseMain="Usar configurações principais do OBS"
|
||||
NewVersion="Nova versão (%1) disponível <a href='https://aitum.tv/download/vertical/'>aqui</a>"
|
||||
RecordPathError="O Caminho do Local de Salvamento está vazio. Por favor verifique seu Caminho do Local de Salvamento em Configurações Verticais → Gravação."
|
||||
Help="Ajuda"
|
||||
HelpSupport="Entrar no Servidor de Discord do Aitum"
|
||||
CopyFromMain="Copiar do principal"
|
||||
VirtualCameraWarning="Após iniciar a câmera virtual vertical, pode ser que você não consiga editar algumas configurações do OBS, pois ele vai achar que ela ainda está ativa após ter paradoo. Reinicie o OBS para conseguir editar as configurações novamente."
|
||||
NoOutputServer="Nenhum servidor de saída"
|
||||
NoOutputServerWarning="Nenhum servidor de saída ativo foi encontrado. Certifique-se que você t possui uma saída ativa nas configurações de transmissão do Vertical."
|
||||
StartAll="Iniciar todos"
|
||||
StopAll="Parar todos"
|
||||
HelpIntro="Encontrou um problema? Quer aprender mais sobre o Vertical? Veja os nossos recursos de ajuda abaixo."
|
||||
HelpTroubleshooterButton="Visitar o website de Soluções de Problemas do Vertical"
|
||||
HelpGuideButton="Visitar o webiste de Regras do Vertical"
|
||||
HelpDiscordButton="Visitar o Discord do Aitum"
|
||||
VirtualCameraModeVertical="Vertical"
|
||||
VirtualCameraModeMain="Principal"
|
||||
VirtualCameraModeBoth="Ambos"
|
||||
StreamingMatchMain="Começar e parar a transmissão quando o OBS principal começar e parar de transmitir"
|
||||
RecordingMatchMain="Começar e parar de gravar quando o OBS principal começar e parar de gravar"
|
||||
68
data/locale/ru-RU.ini
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
Vertical="Vertical"
|
||||
VerticalCanvas="Vertical Canvas"
|
||||
Description="Vertical Canvas"
|
||||
Canvas="Холст"
|
||||
VirtualCam="Виртуальная камера"
|
||||
LinkedScenes="Связанные сцены"
|
||||
OnMainCanvas="Показать на главном холсте"
|
||||
SceneName="Название сцены"
|
||||
SourceName="Название источника"
|
||||
TransitionName="Название перехода"
|
||||
Saving="Backtrack сохранение..."
|
||||
Saved="Backtrack сохранен!"
|
||||
VerticalSettings="Vertical Настройки"
|
||||
General="Основные"
|
||||
Resolution="Разрешение"
|
||||
ShowScenes="Показать вертикальные сцены в основном списке"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack будет включен во время трансляции/записи"
|
||||
BacktrackOn="Backtrack включен"
|
||||
BacktrackOff="Backtrack выключен"
|
||||
BacktrackDuration="Backtrack продолжительность записи"
|
||||
BacktrackPath="Backtrack путь до записи"
|
||||
StartBacktrackHotkey="Горячая клавиша запуска Backtrack"
|
||||
StopBacktrackHotkey="Горячая клавиша остановки Backtrack"
|
||||
SaveBacktrackHotkey="Горячая клавиша сохранения Backtrack"
|
||||
Streaming="Трансляция"
|
||||
ViewGuide="Посмотреть руководство"
|
||||
Name="Имя"
|
||||
Server="Сервер"
|
||||
Key="Ключ"
|
||||
Enabled="Включено"
|
||||
Output="Вывод"
|
||||
StartStreamingHotkey="Горячая клавиша запуска трансляции"
|
||||
StopStreamingHotkey="Горячая клавиша остановки трансляции"
|
||||
Recording="Запись"
|
||||
StartRecordingHotkey="Горячая клавиша начала записи"
|
||||
StopRecordingHotkey="Горячая клавиша остановки записи"
|
||||
Version="Версия"
|
||||
MadeBy="сделано с ♡ в "
|
||||
AddVerticalScene="Добавить вертикальную сцену"
|
||||
RemoveVerticalScene="Удалить вертикальную сцену"
|
||||
StreamVertical="Транслировать все вертикальные выводы"
|
||||
StreamVerticalMulti="Управление отдельными вертикальными выводами"
|
||||
RecordVertical="Vertical Запись"
|
||||
BacktrackClipVertical="Backtrack клип"
|
||||
VirtualCameraVertical="Vertical виртуальная камера"
|
||||
VerticalScene="Vertical Сцена"
|
||||
tiktokError="Пожалуйста, убедитесь, что ключ странсляции обновлен! TikTok обновляет ключ для каждой новой трансляции!"
|
||||
backtrackStartFail="Backtrack ошибка запуска"
|
||||
backtrackCustomFfmpeg="Невозможно запустить Backtrack пока запись настроена с ffmpeg"
|
||||
backtrackNoReplayBuffer="Нельзя запусть Backtrack если нет буфера повтора"
|
||||
UseMain="Использовать основные настройки OBS"
|
||||
NewVersion="Доступна <a href='https://aitum.tv/download/vertical/'>новая версия</a> (%1)"
|
||||
RecordPathError="Указанный путь для записи пуст. Пожалуйста, проверьте путь в разделе Vertical Настройки → Запись."
|
||||
Help="Помощь"
|
||||
HelpSupport="Подключиться к Discord серверу Aitum"
|
||||
CopyFromMain="Скопировать из основного"
|
||||
VirtualCameraWarning="После запуска Vertical виртуальной камеры вы не сможете изменить некоторые настройки OBS без перезапуска OBS."
|
||||
NoOutputServer="Нет сервера вывода"
|
||||
NoOutputServerWarning="Не найден сервер вывода. Убедитесь, что у вас включен вывод в настройках вертикального потока."
|
||||
StartAll="Запустить все"
|
||||
StopAll="Остановить все"
|
||||
HelpIntro="Столкнулись с проблемой? Хотите узнать больше о Vertical? Ознакомьтесь с нашими справочными ресурсами ниже."
|
||||
HelpTroubleshooterButton="Посетите веб-сайт Vertical средства устранения неполадок"
|
||||
HelpGuideButton="Посетите веб-сайт руководства Vertical"
|
||||
HelpDiscordButton="Посетите Aitum Discord"
|
||||
StreamingMatchMain="Запуск и остановка потоковой передачи, когда OBS запускает и останавливает потоковую передачу"
|
||||
RecordingMatchMain="Запуск и остановка записи, когда OBS запускает и останавливает запись"
|
||||
63
data/locale/sv-SE.ini
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
Vertical="Vertikal"
|
||||
VerticalCanvas="Vertikal Kanvas"
|
||||
Description="Vertikal Kanvas"
|
||||
Canvas="Kanvas"
|
||||
VirtualCam="Virtuell kamera"
|
||||
LinkedScenes="Länkade scener"
|
||||
OnMainCanvas="På huvud arbetsytan"
|
||||
SceneName="scen namn"
|
||||
SourceName="käll namn"
|
||||
TransitionName="Övergångs namn"
|
||||
Saving="Sparar Backtrack..."
|
||||
Saved="Backtrack Sparat!"
|
||||
VerticalSettings="Vertikala inställningar"
|
||||
General="Allmänt"
|
||||
Resolution="Upplösning"
|
||||
ShowScenes="Visa Vertikala scener i huvud scenlista"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="Backtrack kör medans strömning/inspelning"
|
||||
BacktrackAlwaysOn="Backtrack alltid på"
|
||||
BacktrackDuration="Backtrack inspelningens varaktighet"
|
||||
BacktrackPath="Backtrack filspar väg"
|
||||
SaveBacktrackHotkey="Save Backtrack snabtangent"
|
||||
Streaming="Strömning"
|
||||
ViewGuide="Visa Guide"
|
||||
Name="Namn"
|
||||
Server="Server"
|
||||
Key="Nyckel"
|
||||
Enabled="Aktivera"
|
||||
Output="Utgång"
|
||||
StartStreamingHotkey="Snabbtangent: Starta ström"
|
||||
StopStreamingHotkey="Snabbtangent: Stoppa ström"
|
||||
Recording="Inspelning"
|
||||
StartRecordingHotkey="Snabbtangent: Starta inspelning"
|
||||
StopRecordingHotkey="Snabbtangent: Stoppa inspelning"
|
||||
Version="Version"
|
||||
MadeBy="Skapad med ♡ av"
|
||||
AddVerticalScene="Lägg till vertikal scen"
|
||||
RemoveVerticalScene="Ta bort vertikal scen"
|
||||
StreamVertical="Strömma alla vertikala utgångar"
|
||||
StreamVerticalMulti="Styr individuella vertikala utgångar"
|
||||
RecordVertical="Spela in vertikalt"
|
||||
BacktrackClipVertical="Backtrack klipp vertikalt"
|
||||
VirtualCameraVertical="Vertikal virtuell kamera"
|
||||
VerticalScene="Vertikal Scen"
|
||||
tiktokError="Dessutom, eftersom vi har upptäckt att du försöker streama till TikTok, se till att din streamnyckel uppdateras när TikTok cyklar dem efter varje stream!"
|
||||
backtrackStartFail="Backtrack start misslyckades"
|
||||
backtrackCustomFfmpeg="Du kan inte aktivera Backtrack om inspelning är inställd på anpassad ffmpeg"
|
||||
backtrackNoReplayBuffer="Du kan inte aktivera Backtrack om ingen återuppspelning inspelning hittas"
|
||||
UseMain="Använd OBS huvud inställningar"
|
||||
NewVersion="Ny version (%1) tillgänglig <a href='https://aitum.tv/download/vertical/'>här</a>"
|
||||
RecordPathError="Den konfigurerade inspelningssökvägen är tom. Kontrollera din inspelningsväg under Vertikala inställningar → Inspelning."
|
||||
Help="Hjälp"
|
||||
HelpSupport="Gå med i Aitum Discord Server"
|
||||
CopyFromMain="Kopiera från main"
|
||||
VirtualCameraWarning="Efter att ha startat den vertikala virtuella kameran kanske du inte kan redigera vissa OBS-inställningar eftersom den tror att den fortfarande är aktiv efter att den stoppats. Starta om OBS för att kunna redigera inställningarna igen."
|
||||
NoOutputServer="Ingen utdataserver"
|
||||
NoOutputServerWarning="Det finns ingen aktiverad utdataserver. Se till att du har en utgång aktiverad i inställningarna för vertikal streaming."
|
||||
StartAll="Starta allt"
|
||||
StopAll="Stoppa allt"
|
||||
HelpIntro="Stöter du på ett problem? Vill du lära dig mer om Vertical? Kolla in våra hjälpresurser nedan."
|
||||
HelpTroubleshooterButton="Besök webbplatsen för Vertical Troubleshooter"
|
||||
HelpGuideButton="Besök webbplatsen Vertical Guides"
|
||||
HelpDiscordButton="Besök Aitum Discord"
|
||||
72
data/locale/zh-CN.ini
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
Vertical="垂直"
|
||||
VerticalCanvas="垂直画布"
|
||||
Description="垂直画布"
|
||||
Canvas="画布"
|
||||
VirtualCam="虚拟摄像头"
|
||||
LinkedScenes="关联场景"
|
||||
OnMainCanvas="在主画布显示"
|
||||
SceneName="场景名称"
|
||||
SourceName="来源名称"
|
||||
TransitionName="转场名称"
|
||||
Saving="正在保存Backtrack..."
|
||||
Saved="Backtrack已保存!"
|
||||
VerticalSettings="垂直设置"
|
||||
General="通用"
|
||||
Resolution="分辨率"
|
||||
ShowScenes="在主场景列表中显示垂直场景"
|
||||
Backtrack="Backtrack"
|
||||
BacktrackEnable="在流媒体/录制时运行Backtrack"
|
||||
BacktrackOn="Backtrack开启"
|
||||
BacktrackOff="Backtrack关闭"
|
||||
BacktrackDuration="Backtrack录制长度"
|
||||
BacktrackPath="Backtrack录制路径"
|
||||
StartBacktrackHotkey="开始Backtrack热键"
|
||||
StopBacktrackHotkey="停止Backtrack热键"
|
||||
SaveBacktrackHotkey="保存Backtrack热键"
|
||||
Streaming="流媒体"
|
||||
ViewGuide="查看指南"
|
||||
Name="名称"
|
||||
Server="服务器"
|
||||
Key="密钥"
|
||||
Enabled="启用"
|
||||
Output="输出"
|
||||
StartStreamingHotkey="开始流媒体热键"
|
||||
StopStreamingHotkey="停止流媒体热键"
|
||||
Recording="录制"
|
||||
StartRecordingHotkey="开始录制热键"
|
||||
StopRecordingHotkey="停止录制热键"
|
||||
Version="版本"
|
||||
MadeBy="由♡制作"
|
||||
AddVerticalScene="添加垂直场景"
|
||||
RemoveVerticalScene="删除垂直场景"
|
||||
StreamVertical="流媒体所有垂直输出"
|
||||
StreamVerticalMulti="控制单个垂直输出"
|
||||
RecordVertical="录制垂直"
|
||||
BacktrackClipVertical="Backtrack剪辑垂直"
|
||||
VirtualCameraVertical="垂直虚拟摄像头"
|
||||
VerticalScene="垂直场景"
|
||||
tiktokError="此外,因为我们检测到您正在尝试流媒体到TikTok,请确保您的流媒体密钥已更新,因为TikTok会在每次流媒体后循环它们!"
|
||||
backtrackStartFail="Backtrack启动失败"
|
||||
backtrackCustomFfmpeg="当录制设置为自定义ffmpeg时,无法启动Backtrack"
|
||||
backtrackNoReplayBuffer="找不到重放缓冲区时,无法启动Backtrack"
|
||||
UseMain="使用主OBS设置"
|
||||
NewVersion="新版本(%1)可用<a href='https://aitum.tv/download/vertical/'>在这里</a>"
|
||||
RecordPathError="配置的录制路径为空。请在垂直设置→录制中检查您的录制路径。"
|
||||
Help="帮助"
|
||||
HelpSupport="加入Aitum Discord服务器"
|
||||
CopyFromMain="从主复制"
|
||||
VirtualCameraWarning="启动垂直虚拟摄像头后,您可能无法编辑某些OBS设置,因为它认为在停止后仍处于活动状态。重新启动OBS以再次编辑设置。"
|
||||
NoOutputServer="没有输出服务器"
|
||||
NoOutputServerWarning="找不到启用的输出服务器。请确保在垂直流媒体设置中启用了输出。"
|
||||
StartAll="全部开始"
|
||||
StopAll="全部停止"
|
||||
HelpIntro="遇到问题?想了解更多关于垂直的信息?请查看下面的帮助资源。"
|
||||
HelpTroubleshooterButton="访问垂直故障排除网站"
|
||||
HelpGuideButton="访问垂直指南网站"
|
||||
HelpDiscordButton="访问Aitum Discord"
|
||||
VirtualCameraModeVertical="垂直"
|
||||
VirtualCameraModeMain="主"
|
||||
VirtualCameraModeBoth="两者"
|
||||
StreamingMatchMain="当主OBS开始和停止流媒体时,开始和停止流媒体"
|
||||
RecordingMatchMain="当主OBS开始和停止录制时,开始和停止录制"
|
||||
OutputsMultistream="由于您正在使用Aitum Multistream,您必须从Aitum Multistream设置中控制垂直流媒体输出。\n在此处禁用更改输出设置。"
|
||||
138
display-helpers.hpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <graphics/vec4.h>
|
||||
#include <graphics/matrix4.h>
|
||||
|
||||
static inline void GetScaleAndCenterPos(int baseCX, int baseCY, int windowCX, int windowCY, int &x, int &y, float &scale)
|
||||
{
|
||||
double windowAspect, baseAspect;
|
||||
int newCX, newCY;
|
||||
|
||||
windowAspect = double(windowCX) / double(windowCY);
|
||||
baseAspect = double(baseCX) / double(baseCY);
|
||||
|
||||
if (windowAspect > baseAspect) {
|
||||
scale = float(windowCY) / float(baseCY);
|
||||
newCX = int(double(windowCY) * baseAspect);
|
||||
newCY = windowCY;
|
||||
} else {
|
||||
scale = float(windowCX) / float(baseCX);
|
||||
newCX = windowCX;
|
||||
newCY = int(float(windowCX) / baseAspect);
|
||||
}
|
||||
|
||||
x = windowCX / 2 - newCX / 2;
|
||||
y = windowCY / 2 - newCY / 2;
|
||||
}
|
||||
|
||||
static inline void GetCenterPosFromFixedScale(int baseCX, int baseCY, int windowCX, int windowCY, int &x, int &y, float scale)
|
||||
{
|
||||
x = (int)((float(windowCX) - float(baseCX) * scale) / 2.0f);
|
||||
y = (int)((float(windowCY) - float(baseCY) * scale) / 2.0f);
|
||||
}
|
||||
|
||||
static inline QSize GetPixelSize(QWidget *widget)
|
||||
{
|
||||
return widget->size() * widget->devicePixelRatioF();
|
||||
}
|
||||
|
||||
#define OUTLINE_COLOR 0xFFD0D0D0
|
||||
#define LINE_LENGTH 0.1f
|
||||
|
||||
// Rec. ITU-R BT.1848-1 / EBU R 95
|
||||
#define ACTION_SAFE_PERCENT 0.035f // 3.5%
|
||||
#define GRAPHICS_SAFE_PERCENT 0.05f // 5.0%
|
||||
#define FOURBYTHREE_SAFE_PERCENT 0.1625f // 16.25%
|
||||
|
||||
static inline void InitSafeAreas(gs_vertbuffer_t **actionSafeMargin, gs_vertbuffer_t **graphicsSafeMargin,
|
||||
gs_vertbuffer_t **fourByThreeSafeMargin, gs_vertbuffer_t **leftLine, gs_vertbuffer_t **topLine,
|
||||
gs_vertbuffer_t **rightLine)
|
||||
{
|
||||
obs_enter_graphics();
|
||||
|
||||
// All essential action should be placed inside this area
|
||||
gs_render_start(true);
|
||||
gs_vertex2f(ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT);
|
||||
gs_vertex2f(ACTION_SAFE_PERCENT, 1 - ACTION_SAFE_PERCENT);
|
||||
gs_vertex2f(1 - ACTION_SAFE_PERCENT, 1 - ACTION_SAFE_PERCENT);
|
||||
gs_vertex2f(1 - ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT);
|
||||
gs_vertex2f(ACTION_SAFE_PERCENT, ACTION_SAFE_PERCENT);
|
||||
*actionSafeMargin = gs_render_save();
|
||||
|
||||
// All graphics should be placed inside this area
|
||||
gs_render_start(true);
|
||||
gs_vertex2f(GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(GRAPHICS_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(1 - GRAPHICS_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(1 - GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(GRAPHICS_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
|
||||
*graphicsSafeMargin = gs_render_save();
|
||||
|
||||
// 4:3 safe area for widescreen
|
||||
gs_render_start(true);
|
||||
gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(1 - FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(1 - FOURBYTHREE_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, 1 - GRAPHICS_SAFE_PERCENT);
|
||||
gs_vertex2f(FOURBYTHREE_SAFE_PERCENT, GRAPHICS_SAFE_PERCENT);
|
||||
*fourByThreeSafeMargin = gs_render_save();
|
||||
|
||||
gs_render_start(true);
|
||||
gs_vertex2f(0.0f, 0.5f);
|
||||
gs_vertex2f(LINE_LENGTH, 0.5f);
|
||||
*leftLine = gs_render_save();
|
||||
|
||||
gs_render_start(true);
|
||||
gs_vertex2f(0.5f, 0.0f);
|
||||
gs_vertex2f(0.5f, LINE_LENGTH);
|
||||
*topLine = gs_render_save();
|
||||
|
||||
gs_render_start(true);
|
||||
gs_vertex2f(1.0f, 0.5f);
|
||||
gs_vertex2f(1 - LINE_LENGTH, 0.5f);
|
||||
*rightLine = gs_render_save();
|
||||
|
||||
obs_leave_graphics();
|
||||
}
|
||||
|
||||
static inline void RenderSafeAreas(gs_vertbuffer_t *vb, int cx, int cy)
|
||||
{
|
||||
if (!vb)
|
||||
return;
|
||||
|
||||
matrix4 transform;
|
||||
matrix4_identity(&transform);
|
||||
transform.x.x = (float)cx;
|
||||
transform.y.y = (float)cy;
|
||||
|
||||
gs_load_vertexbuffer(vb);
|
||||
|
||||
gs_matrix_push();
|
||||
gs_matrix_mul(&transform);
|
||||
|
||||
gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
|
||||
gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
|
||||
|
||||
gs_effect_set_color(color, OUTLINE_COLOR);
|
||||
while (gs_effect_loop(solid, "Solid"))
|
||||
gs_draw(GS_LINESTRIP, 0, 0);
|
||||
|
||||
gs_matrix_pop();
|
||||
}
|
||||
144
file-updater.c
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#include <curl/curl.h>
|
||||
#include <util/threading.h>
|
||||
#include <util/platform.h>
|
||||
#include <util/darray.h>
|
||||
#include <util/dstr.h>
|
||||
#include <obs-data.h>
|
||||
#include "file-updater.h"
|
||||
|
||||
#define warn(msg, ...) blog(LOG_WARNING, "%s" msg, info->log_prefix, ##__VA_ARGS__)
|
||||
#define info(msg, ...) blog(LOG_WARNING, "%s" msg, info->log_prefix, ##__VA_ARGS__)
|
||||
|
||||
struct update_info {
|
||||
char error[CURL_ERROR_SIZE];
|
||||
struct curl_slist *header;
|
||||
DARRAY(uint8_t) file_data;
|
||||
char *user_agent;
|
||||
CURL *curl;
|
||||
char *url;
|
||||
|
||||
confirm_file_callback_t callback;
|
||||
void *param;
|
||||
|
||||
pthread_t thread;
|
||||
bool thread_created;
|
||||
char *log_prefix;
|
||||
};
|
||||
|
||||
void update_info_destroy(struct update_info *info)
|
||||
{
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
if (info->thread_created)
|
||||
pthread_join(info->thread, NULL);
|
||||
|
||||
da_free(info->file_data);
|
||||
bfree(info->log_prefix);
|
||||
bfree(info->user_agent);
|
||||
bfree(info->url);
|
||||
|
||||
if (info->header)
|
||||
curl_slist_free_all(info->header);
|
||||
if (info->curl)
|
||||
curl_easy_cleanup(info->curl);
|
||||
bfree(info);
|
||||
}
|
||||
|
||||
static size_t http_write(void *ptr, size_t size, size_t nmemb, void *uinfo)
|
||||
{
|
||||
size_t total = size * nmemb;
|
||||
struct update_info *info = (struct update_info *)uinfo;
|
||||
|
||||
if (total)
|
||||
da_push_back_array(info->file_data, ptr, total);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static bool do_http_request(struct update_info *info, const char *url, long *response_code)
|
||||
{
|
||||
CURLcode code;
|
||||
uint8_t null_terminator = 0;
|
||||
|
||||
da_resize(info->file_data, 0);
|
||||
curl_easy_setopt(info->curl, CURLOPT_URL, url);
|
||||
curl_easy_setopt(info->curl, CURLOPT_HTTPHEADER, info->header);
|
||||
curl_easy_setopt(info->curl, CURLOPT_ERRORBUFFER, info->error);
|
||||
curl_easy_setopt(info->curl, CURLOPT_WRITEFUNCTION, http_write);
|
||||
curl_easy_setopt(info->curl, CURLOPT_WRITEDATA, info);
|
||||
curl_easy_setopt(info->curl, CURLOPT_FAILONERROR, 1L);
|
||||
curl_easy_setopt(info->curl, CURLOPT_NOSIGNAL, 1L);
|
||||
curl_easy_setopt(info->curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||
curl_easy_setopt(info->curl, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_REVOKE_BEST_EFFORT);
|
||||
|
||||
code = curl_easy_perform(info->curl);
|
||||
if (code != CURLE_OK) {
|
||||
warn("Remote update of URL \"%s\" failed: %s", url, info->error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (curl_easy_getinfo(info->curl, CURLINFO_RESPONSE_CODE, response_code) != CURLE_OK)
|
||||
return false;
|
||||
|
||||
if (*response_code >= 400) {
|
||||
warn("Remote update of URL \"%s\" failed: HTTP/%ld", url, *response_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
da_push_back(info->file_data, &null_terminator);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct file_update_data {
|
||||
const char *name;
|
||||
int version;
|
||||
bool newer;
|
||||
bool found;
|
||||
};
|
||||
|
||||
static void *single_file_thread(void *data)
|
||||
{
|
||||
struct update_info *info = data;
|
||||
struct file_download_data download_data;
|
||||
long response_code;
|
||||
|
||||
info->curl = curl_easy_init();
|
||||
if (!info->curl) {
|
||||
warn("Could not initialize Curl");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!do_http_request(info, info->url, &response_code))
|
||||
return NULL;
|
||||
if (!info->file_data.array || !info->file_data.array[0])
|
||||
return NULL;
|
||||
|
||||
download_data.name = info->url;
|
||||
download_data.version = 0;
|
||||
download_data.buffer.da = info->file_data.da;
|
||||
info->callback(info->param, &download_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
update_info_t *update_info_create_single(const char *log_prefix, const char *user_agent, const char *file_url,
|
||||
confirm_file_callback_t confirm_callback, void *param)
|
||||
{
|
||||
struct update_info *info;
|
||||
|
||||
if (!log_prefix)
|
||||
log_prefix = "";
|
||||
|
||||
info = bzalloc(sizeof(*info));
|
||||
info->log_prefix = bstrdup(log_prefix);
|
||||
info->user_agent = bstrdup(user_agent);
|
||||
info->url = bstrdup(file_url);
|
||||
info->callback = confirm_callback;
|
||||
info->param = param;
|
||||
|
||||
if (pthread_create(&info->thread, NULL, single_file_thread, info) == 0)
|
||||
info->thread_created = true;
|
||||
|
||||
return info;
|
||||
}
|
||||
19
file-updater.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include <util/darray.h>
|
||||
|
||||
struct update_info;
|
||||
typedef struct update_info update_info_t;
|
||||
|
||||
struct file_download_data {
|
||||
const char *name;
|
||||
int version;
|
||||
|
||||
DARRAY(uint8_t) buffer;
|
||||
};
|
||||
|
||||
typedef bool (*confirm_file_callback_t)(void *param, struct file_download_data *file);
|
||||
|
||||
update_info_t *update_info_create_single(const char *log_prefix, const char *user_agent, const char *file_url,
|
||||
confirm_file_callback_t confirm_callback, void *param);
|
||||
void update_info_destroy(update_info_t *info);
|
||||
498
hotkey-edit.cpp
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "hotkey-edit.hpp"
|
||||
|
||||
#include <util/dstr.hpp>
|
||||
#include <QPointer>
|
||||
#include <QStyle>
|
||||
#include <QAction>
|
||||
|
||||
#include "obs-frontend-api.h"
|
||||
#include "obs-module.h"
|
||||
|
||||
uint32_t TranslateQtKeyboardEventModifiers(Qt::KeyboardModifiers mods)
|
||||
{
|
||||
int obsModifiers = INTERACT_NONE;
|
||||
|
||||
if (mods.testFlag(Qt::ShiftModifier))
|
||||
obsModifiers |= INTERACT_SHIFT_KEY;
|
||||
if (mods.testFlag(Qt::AltModifier))
|
||||
obsModifiers |= INTERACT_ALT_KEY;
|
||||
#ifdef __APPLE__
|
||||
// Mac: Meta = Control, Control = Command
|
||||
if (mods.testFlag(Qt::ControlModifier))
|
||||
obsModifiers |= INTERACT_COMMAND_KEY;
|
||||
if (mods.testFlag(Qt::MetaModifier))
|
||||
obsModifiers |= INTERACT_CONTROL_KEY;
|
||||
#else
|
||||
// Handle windows key? Can a browser even trap that key?
|
||||
if (mods.testFlag(Qt::ControlModifier))
|
||||
obsModifiers |= INTERACT_CONTROL_KEY;
|
||||
if (mods.testFlag(Qt::MetaModifier))
|
||||
obsModifiers |= INTERACT_COMMAND_KEY;
|
||||
|
||||
#endif
|
||||
|
||||
return obsModifiers;
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->isAutoRepeat())
|
||||
return;
|
||||
|
||||
obs_key_combination_t new_key;
|
||||
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Shift:
|
||||
case Qt::Key_Control:
|
||||
case Qt::Key_Alt:
|
||||
case Qt::Key_Meta:
|
||||
new_key.key = OBS_KEY_NONE;
|
||||
break;
|
||||
|
||||
#ifdef __APPLE__
|
||||
case Qt::Key_CapsLock:
|
||||
// kVK_CapsLock == 57
|
||||
new_key.key = obs_key_from_virtual_key(57);
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
new_key.key = obs_key_from_virtual_key(event->nativeVirtualKey());
|
||||
}
|
||||
|
||||
new_key.modifiers = TranslateQtKeyboardEventModifiers(event->modifiers());
|
||||
|
||||
HandleNewKey(new_key);
|
||||
}
|
||||
|
||||
QVariant OBSHotkeyEdit::inputMethodQuery(Qt::InputMethodQuery query) const
|
||||
{
|
||||
if (query == Qt::ImEnabled) {
|
||||
return false;
|
||||
} else {
|
||||
return QLineEdit::inputMethodQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
void OBSHotkeyEdit::keyReleaseEvent(QKeyEvent *event)
|
||||
{
|
||||
if (event->isAutoRepeat())
|
||||
return;
|
||||
|
||||
if (event->key() != Qt::Key_CapsLock)
|
||||
return;
|
||||
|
||||
obs_key_combination_t new_key;
|
||||
|
||||
// kVK_CapsLock == 57
|
||||
new_key.key = obs_key_from_virtual_key(57);
|
||||
new_key.modifiers = TranslateQtKeyboardEventModifiers(event->modifiers());
|
||||
|
||||
HandleNewKey(new_key);
|
||||
}
|
||||
#endif
|
||||
|
||||
void OBSHotkeyEdit::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
obs_key_combination_t new_key;
|
||||
|
||||
switch (event->button()) {
|
||||
case Qt::NoButton:
|
||||
case Qt::LeftButton:
|
||||
case Qt::RightButton:
|
||||
case Qt::AllButtons:
|
||||
case Qt::MouseButtonMask:
|
||||
return;
|
||||
|
||||
case Qt::MiddleButton:
|
||||
new_key.key = OBS_KEY_MOUSE3;
|
||||
break;
|
||||
|
||||
#define MAP_BUTTON(i, j) \
|
||||
case Qt::ExtraButton##i: \
|
||||
new_key.key = OBS_KEY_MOUSE##j; \
|
||||
break;
|
||||
MAP_BUTTON(1, 4)
|
||||
MAP_BUTTON(2, 5)
|
||||
MAP_BUTTON(3, 6)
|
||||
MAP_BUTTON(4, 7)
|
||||
MAP_BUTTON(5, 8)
|
||||
MAP_BUTTON(6, 9)
|
||||
MAP_BUTTON(7, 10)
|
||||
MAP_BUTTON(8, 11)
|
||||
MAP_BUTTON(9, 12)
|
||||
MAP_BUTTON(10, 13)
|
||||
MAP_BUTTON(11, 14)
|
||||
MAP_BUTTON(12, 15)
|
||||
MAP_BUTTON(13, 16)
|
||||
MAP_BUTTON(14, 17)
|
||||
MAP_BUTTON(15, 18)
|
||||
MAP_BUTTON(16, 19)
|
||||
MAP_BUTTON(17, 20)
|
||||
MAP_BUTTON(18, 21)
|
||||
MAP_BUTTON(19, 22)
|
||||
MAP_BUTTON(20, 23)
|
||||
MAP_BUTTON(21, 24)
|
||||
MAP_BUTTON(22, 25)
|
||||
MAP_BUTTON(23, 26)
|
||||
MAP_BUTTON(24, 27)
|
||||
#undef MAP_BUTTON
|
||||
}
|
||||
|
||||
new_key.modifiers = TranslateQtKeyboardEventModifiers(event->modifiers());
|
||||
|
||||
HandleNewKey(new_key);
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::HandleNewKey(obs_key_combination_t new_key)
|
||||
{
|
||||
if (new_key == key || obs_key_combination_is_empty(new_key))
|
||||
return;
|
||||
|
||||
key = new_key;
|
||||
|
||||
changed = true;
|
||||
emit KeyChanged(key);
|
||||
|
||||
RenderKey();
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::RenderKey()
|
||||
{
|
||||
DStr str;
|
||||
obs_key_combination_to_str(key, str);
|
||||
|
||||
setText(QString::fromUtf8(str->array));
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::ResetKey()
|
||||
{
|
||||
key = original;
|
||||
|
||||
changed = false;
|
||||
emit KeyChanged(key);
|
||||
|
||||
RenderKey();
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::ClearKey()
|
||||
{
|
||||
key = {0, OBS_KEY_NONE};
|
||||
|
||||
changed = true;
|
||||
emit KeyChanged(key);
|
||||
|
||||
RenderKey();
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::UpdateDuplicationState()
|
||||
{
|
||||
if (dupeIcon && dupeIcon->isVisible() != hasDuplicate) {
|
||||
dupeIcon->setVisible(hasDuplicate);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::InitSignalHandler()
|
||||
{
|
||||
layoutChanged = {obs_get_signal_handler(), "hotkey_layout_change",
|
||||
[](void *this_, calldata_t *) {
|
||||
auto edit = static_cast<OBSHotkeyEdit *>(this_);
|
||||
QMetaObject::invokeMethod(edit, "ReloadKeyLayout");
|
||||
},
|
||||
this};
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::CreateDupeIcon()
|
||||
{
|
||||
dupeIcon = addAction(QIcon::fromTheme("obs", QIcon(":/res/images/warning.svg")), ActionPosition::TrailingPosition);
|
||||
dupeIcon->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Hotkeys.DuplicateWarning")));
|
||||
QObject::connect(dupeIcon, &QAction::triggered, [=] { emit SearchKey(key); });
|
||||
dupeIcon->setVisible(false);
|
||||
}
|
||||
|
||||
void OBSHotkeyEdit::ReloadKeyLayout()
|
||||
{
|
||||
RenderKey();
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::SetKeyCombinations(const std::vector<obs_key_combination_t> &combos)
|
||||
{
|
||||
if (combos.empty())
|
||||
AddEdit({0, OBS_KEY_NONE});
|
||||
|
||||
for (auto combo : combos)
|
||||
AddEdit(combo);
|
||||
}
|
||||
|
||||
bool OBSHotkeyWidget::Changed() const
|
||||
{
|
||||
return changed || std::any_of(begin(edits), end(edits), [](OBSHotkeyEdit *edit) { return edit->changed; });
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::Apply()
|
||||
{
|
||||
for (auto &edit : edits) {
|
||||
edit->original = edit->key;
|
||||
edit->changed = false;
|
||||
}
|
||||
|
||||
changed = false;
|
||||
|
||||
for (auto &revertButton : revertButtons)
|
||||
revertButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::GetCombinations(std::vector<obs_key_combination_t> &combinations) const
|
||||
{
|
||||
combinations.clear();
|
||||
for (auto &edit : edits)
|
||||
if (!obs_key_combination_is_empty(edit->key))
|
||||
combinations.emplace_back(edit->key);
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::Save()
|
||||
{
|
||||
std::vector<obs_key_combination_t> combinations;
|
||||
Save(combinations);
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::Save(std::vector<obs_key_combination_t> &combinations)
|
||||
{
|
||||
GetCombinations(combinations);
|
||||
Apply();
|
||||
|
||||
auto AtomicUpdate = [&]() {
|
||||
ignoreChangedBindings = true;
|
||||
|
||||
obs_hotkey_load_bindings(id, combinations.data(), combinations.size());
|
||||
|
||||
ignoreChangedBindings = false;
|
||||
};
|
||||
using AtomicUpdate_t = decltype(&AtomicUpdate);
|
||||
|
||||
obs_hotkey_update_atomic([](void *d) { (*static_cast<AtomicUpdate_t>(d))(); }, static_cast<void *>(&AtomicUpdate));
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx)
|
||||
{
|
||||
auto edit = new OBSHotkeyEdit(parentWidget(), combo);
|
||||
edit->setToolTip(toolTip);
|
||||
|
||||
auto revert = new QPushButton;
|
||||
revert->setProperty("themeID", "revertIcon");
|
||||
revert->setProperty("class", "icon-revert");
|
||||
revert->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Revert")));
|
||||
revert->setEnabled(false);
|
||||
|
||||
auto clear = new QPushButton;
|
||||
clear->setProperty("themeID", "clearIconSmall");
|
||||
clear->setProperty("class", "icon-trash");
|
||||
clear->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Clear")));
|
||||
clear->setEnabled(!obs_key_combination_is_empty(combo));
|
||||
|
||||
QObject::connect(edit, &OBSHotkeyEdit::KeyChanged, [=](obs_key_combination_t new_combo) {
|
||||
clear->setEnabled(!obs_key_combination_is_empty(new_combo));
|
||||
revert->setEnabled(edit->original != new_combo);
|
||||
});
|
||||
|
||||
auto add = new QPushButton;
|
||||
add->setProperty("themeID", "addIconSmall");
|
||||
add->setProperty("class", "icon-plus");
|
||||
add->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Add")));
|
||||
|
||||
auto remove = new QPushButton;
|
||||
remove->setProperty("themeID", "removeIconSmall");
|
||||
remove->setProperty("class", "icon-minus");
|
||||
remove->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Remove")));
|
||||
remove->setEnabled(removeButtons.size() > 0);
|
||||
|
||||
auto CurrentIndex = [&, remove] {
|
||||
auto res = std::find(begin(removeButtons), end(removeButtons), remove);
|
||||
return std::distance(begin(removeButtons), res);
|
||||
};
|
||||
|
||||
QObject::connect(add, &QPushButton::clicked, [&, CurrentIndex] { AddEdit({0, OBS_KEY_NONE}, (int)CurrentIndex() + 1); });
|
||||
|
||||
QObject::connect(remove, &QPushButton::clicked, [&, CurrentIndex] { RemoveEdit(CurrentIndex()); });
|
||||
|
||||
QHBoxLayout *subLayout = new QHBoxLayout;
|
||||
subLayout->setContentsMargins(0, 4, 0, 0);
|
||||
subLayout->addWidget(edit);
|
||||
subLayout->addWidget(revert);
|
||||
subLayout->addWidget(clear);
|
||||
subLayout->addWidget(add);
|
||||
subLayout->addWidget(remove);
|
||||
|
||||
if (removeButtons.size() == 1)
|
||||
removeButtons.front()->setEnabled(true);
|
||||
|
||||
if (idx != -1) {
|
||||
revertButtons.insert(begin(revertButtons) + idx, revert);
|
||||
removeButtons.insert(begin(removeButtons) + idx, remove);
|
||||
edits.insert(begin(edits) + idx, edit);
|
||||
} else {
|
||||
revertButtons.emplace_back(revert);
|
||||
removeButtons.emplace_back(remove);
|
||||
edits.emplace_back(edit);
|
||||
}
|
||||
|
||||
layout()->insertLayout(idx, subLayout);
|
||||
|
||||
QObject::connect(revert, &QPushButton::clicked, edit, &OBSHotkeyEdit::ResetKey);
|
||||
QObject::connect(clear, &QPushButton::clicked, edit, &OBSHotkeyEdit::ClearKey);
|
||||
|
||||
QObject::connect(edit, &OBSHotkeyEdit::KeyChanged, [&](obs_key_combination) { emit KeyChanged(); });
|
||||
QObject::connect(edit, &OBSHotkeyEdit::SearchKey, [=](obs_key_combination c) { emit SearchKey(c); });
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::RemoveEdit(size_t idx, bool signal)
|
||||
{
|
||||
auto &edit = *(begin(edits) + idx);
|
||||
if (!obs_key_combination_is_empty(edit->original) && signal) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
revertButtons.erase(begin(revertButtons) + idx);
|
||||
removeButtons.erase(begin(removeButtons) + idx);
|
||||
edits.erase(begin(edits) + idx);
|
||||
|
||||
auto item = layout()->takeAt(static_cast<int>(idx));
|
||||
QLayoutItem *child = nullptr;
|
||||
while ((child = item->layout()->takeAt(0))) {
|
||||
delete child->widget();
|
||||
delete child;
|
||||
}
|
||||
delete item;
|
||||
|
||||
if (removeButtons.size() == 1)
|
||||
removeButtons.front()->setEnabled(false);
|
||||
|
||||
emit KeyChanged();
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::BindingsChanged(void *data, calldata_t *param)
|
||||
{
|
||||
auto widget = static_cast<OBSHotkeyWidget *>(data);
|
||||
auto key = static_cast<obs_hotkey_t *>(calldata_ptr(param, "key"));
|
||||
|
||||
QMetaObject::invokeMethod(widget, "HandleChangedBindings", Q_ARG(obs_hotkey_id, obs_hotkey_get_id(key)));
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::HandleChangedBindings(obs_hotkey_id id_)
|
||||
{
|
||||
if (ignoreChangedBindings || id != id_)
|
||||
return;
|
||||
|
||||
std::vector<obs_key_combination_t> bindings;
|
||||
auto LoadBindings = [&](obs_hotkey_binding_t *binding) {
|
||||
if (obs_hotkey_binding_get_hotkey_id(binding) != id)
|
||||
return;
|
||||
|
||||
auto get_combo = obs_hotkey_binding_get_key_combination;
|
||||
bindings.push_back(get_combo(binding));
|
||||
};
|
||||
using LoadBindings_t = decltype(&LoadBindings);
|
||||
|
||||
obs_enum_hotkey_bindings(
|
||||
[](void *param, size_t, obs_hotkey_binding_t *binding) {
|
||||
auto LoadBindingsFunc = *static_cast<LoadBindings_t>(param);
|
||||
LoadBindingsFunc(binding);
|
||||
return true;
|
||||
},
|
||||
static_cast<void *>(&LoadBindings));
|
||||
|
||||
while (edits.size() > 0)
|
||||
RemoveEdit(edits.size() - 1, false);
|
||||
|
||||
SetKeyCombinations(bindings);
|
||||
}
|
||||
|
||||
static inline void updateStyle(QWidget *widget)
|
||||
{
|
||||
auto style = widget->style();
|
||||
style->unpolish(widget);
|
||||
style->polish(widget);
|
||||
widget->update();
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void OBSHotkeyWidget::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void OBSHotkeyWidget::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
event->accept();
|
||||
label->highlightPair(true);
|
||||
}
|
||||
|
||||
void OBSHotkeyWidget::leaveEvent(QEvent *event)
|
||||
{
|
||||
if (!label)
|
||||
return;
|
||||
|
||||
event->accept();
|
||||
label->highlightPair(false);
|
||||
}
|
||||
|
||||
void OBSHotkeyLabel::highlightPair(bool highlight)
|
||||
{
|
||||
if (!pairPartner)
|
||||
return;
|
||||
|
||||
pairPartner->setProperty("hotkeyPairHover", highlight);
|
||||
updateStyle(pairPartner);
|
||||
setProperty("hotkeyPairHover", highlight);
|
||||
updateStyle(this);
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void OBSHotkeyLabel::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void OBSHotkeyLabel::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
|
||||
if (!pairPartner)
|
||||
return;
|
||||
|
||||
event->accept();
|
||||
highlightPair(true);
|
||||
}
|
||||
|
||||
void OBSHotkeyLabel::leaveEvent(QEvent *event)
|
||||
{
|
||||
if (!pairPartner)
|
||||
return;
|
||||
|
||||
event->accept();
|
||||
highlightPair(false);
|
||||
}
|
||||
|
||||
void OBSHotkeyLabel::setToolTip(const QString &toolTip)
|
||||
{
|
||||
QLabel::setToolTip(toolTip);
|
||||
if (widget)
|
||||
widget->setToolTip(toolTip);
|
||||
}
|
||||
178
hotkey-edit.hpp
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QLineEdit>
|
||||
#include <QKeyEvent>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <QPointer>
|
||||
#include <QLabel>
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
static inline bool operator!=(const obs_key_combination_t &c1, const obs_key_combination_t &c2)
|
||||
{
|
||||
return c1.modifiers != c2.modifiers || c1.key != c2.key;
|
||||
}
|
||||
|
||||
static inline bool operator==(const obs_key_combination_t &c1, const obs_key_combination_t &c2)
|
||||
{
|
||||
return !(c1 != c2);
|
||||
}
|
||||
|
||||
class OBSHotkeyWidget;
|
||||
|
||||
class OBSHotkeyLabel : public QLabel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QPointer<OBSHotkeyLabel> pairPartner;
|
||||
QPointer<OBSHotkeyWidget> widget;
|
||||
void highlightPair(bool highlight);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
void setToolTip(const QString &toolTip);
|
||||
};
|
||||
|
||||
class OBSHotkeyEdit : public QLineEdit {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
OBSHotkeyEdit(QWidget *parent, obs_key_combination_t original_) : QLineEdit(parent), original(original_)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
// disable the input cursor on OSX, focus should be clear
|
||||
// enough with the default focus frame
|
||||
setReadOnly(true);
|
||||
#endif
|
||||
setAttribute(Qt::WA_InputMethodEnabled, false);
|
||||
setAttribute(Qt::WA_MacShowFocusRect, true);
|
||||
InitSignalHandler();
|
||||
CreateDupeIcon();
|
||||
ResetKey();
|
||||
}
|
||||
|
||||
obs_key_combination_t original;
|
||||
obs_key_combination_t key;
|
||||
bool changed = false;
|
||||
|
||||
void UpdateDuplicationState();
|
||||
bool hasDuplicate = false;
|
||||
QVariant inputMethodQuery(Qt::InputMethodQuery) const override;
|
||||
|
||||
protected:
|
||||
OBSSignal layoutChanged;
|
||||
QAction *dupeIcon;
|
||||
|
||||
void InitSignalHandler();
|
||||
void CreateDupeIcon();
|
||||
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
#ifdef __APPLE__
|
||||
void keyReleaseEvent(QKeyEvent *event) override;
|
||||
#endif
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
||||
void RenderKey();
|
||||
|
||||
public slots:
|
||||
void HandleNewKey(obs_key_combination_t new_key);
|
||||
void ReloadKeyLayout();
|
||||
void ResetKey();
|
||||
void ClearKey();
|
||||
|
||||
signals:
|
||||
void KeyChanged(obs_key_combination_t);
|
||||
void SearchKey(obs_key_combination_t);
|
||||
};
|
||||
|
||||
class OBSHotkeyWidget : public QWidget {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
OBSHotkeyWidget(QWidget *parent, obs_hotkey_id id_, std::string name_, const std::vector<obs_key_combination_t> &combos = {})
|
||||
: QWidget(parent),
|
||||
id(id_),
|
||||
name(name_),
|
||||
bindingsChanged(obs_get_signal_handler(), "hotkey_bindings_changed", &OBSHotkeyWidget::BindingsChanged, this)
|
||||
{
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
|
||||
SetKeyCombinations(combos);
|
||||
}
|
||||
|
||||
void SetKeyCombinations(const std::vector<obs_key_combination_t> &);
|
||||
|
||||
obs_hotkey_id id;
|
||||
std::string name;
|
||||
|
||||
bool changed = false;
|
||||
bool Changed() const;
|
||||
|
||||
QPointer<OBSHotkeyLabel> label;
|
||||
std::vector<QPointer<OBSHotkeyEdit>> edits;
|
||||
|
||||
QString toolTip;
|
||||
void setToolTip(const QString &toolTip_)
|
||||
{
|
||||
toolTip = toolTip_;
|
||||
for (auto &edit : edits)
|
||||
edit->setToolTip(toolTip_);
|
||||
}
|
||||
|
||||
void Apply();
|
||||
void GetCombinations(std::vector<obs_key_combination_t> &) const;
|
||||
void Save();
|
||||
void Save(std::vector<obs_key_combination_t> &combinations);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void AddEdit(obs_key_combination combo, int idx = -1);
|
||||
void RemoveEdit(size_t idx, bool signal = true);
|
||||
|
||||
static void BindingsChanged(void *data, calldata_t *param);
|
||||
|
||||
std::vector<QPointer<QPushButton>> removeButtons;
|
||||
std::vector<QPointer<QPushButton>> revertButtons;
|
||||
OBSSignal bindingsChanged;
|
||||
bool ignoreChangedBindings = false;
|
||||
|
||||
QVBoxLayout *layout() const { return dynamic_cast<QVBoxLayout *>(QWidget::layout()); }
|
||||
|
||||
private slots:
|
||||
void HandleChangedBindings(obs_hotkey_id id_);
|
||||
|
||||
signals:
|
||||
void KeyChanged();
|
||||
void SearchKey(obs_key_combination_t);
|
||||
};
|
||||
BIN
media/aitum.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
5
media/backtrack_off.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="-4 2 48 48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill="#ff0000" stroke="none" d="M 28 26 C 28 21.581722 24.418278 18 20 18 C 15.581722 18 12 21.581722 12 26 C 12 30.418278 15.581722 34 20 34 C 24.418278 34 28 30.418278 28 26 Z"/>
|
||||
<path fill="#1a57ff" stroke="none" d="M 20.111328 49.5 L 20.111328 45.998047 C 20.074188 45.998226 20.037188 46 20 46 C 8.954362 46 0 37.045639 0 26 C -0 20.389647 2.31037 15.319458 6.03125 11.6875 L 10.273438 15.929688 C 7.638605 18.475418 6 22.04689 6 26 C 6 33.731949 12.268052 40 20 40 C 20.03726 40 20.074121 39.998337 20.111328 39.998047 L 20.111328 36.5 L 27.888672 42.759766 L 20.111328 49.5 Z M 33.96875 40.3125 L 29.726563 36.070313 C 32.361397 33.524574 34 29.95311 34 26 C 34 18.268051 27.731949 12 20 12 C 19.96274 12 19.925879 12.001663 19.888672 12.001953 L 19.888672 15.5 L 12.111328 8.759766 L 19.888672 2.5 L 19.888672 6.001953 C 19.925812 6.001774 19.962812 6 20 6 C 31.045637 6 40 14.954361 40 26 C 40 31.610353 37.689636 36.680534 33.96875 40.3125 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
5
media/backtrack_on.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="-4 2 48 48" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill="#ff0000" stroke="none" d="M 28 26 C 28 21.581722 24.418278 18 20 18 C 15.581722 18 12 21.581722 12 26 C 12 30.418278 15.581722 34 20 34 C 24.418278 34 28 30.418278 28 26 Z"/>
|
||||
<path fill="#ffffff" stroke="none" d="M 20.111328 49.5 L 20.111328 45.998047 C 20.074188 45.998226 20.037188 46 20 46 C 8.954362 46 0 37.045639 0 26 C -0 20.389647 2.31037 15.319458 6.03125 11.6875 L 10.273438 15.929688 C 7.638605 18.475418 6 22.04689 6 26 C 6 33.731949 12.268052 40 20 40 C 20.03726 40 20.074121 39.998337 20.111328 39.998047 L 20.111328 36.5 L 27.888672 42.759766 L 20.111328 49.5 Z M 33.96875 40.3125 L 29.726563 36.070313 C 32.361397 33.524574 34 29.95311 34 26 C 34 18.268051 27.731949 12 20 12 C 19.96274 12 19.925879 12.001663 19.888672 12.001953 L 19.888672 15.5 L 12.111328 8.759766 L 19.888672 2.5 L 19.888672 6.001953 C 19.925812 6.001774 19.962812 6 20 6 C 31.045637 6 40 14.954361 40 26 C 40 31.610353 37.689636 36.680534 33.96875 40.3125 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
media/icon.ico
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
media/logo.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
37
media/record.svg
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 2.1166666 2.1166667"
|
||||
height="8"
|
||||
width="8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-294.88332)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="0.52916664"
|
||||
cy="295.94165"
|
||||
cx="1.0583333"
|
||||
id="path4544"
|
||||
style="fill:#ff0000;fill-opacity:1;stroke:none;stroke-width:0.06614584;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
37
media/recording.svg
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 2.1166666 2.1166667"
|
||||
height="8"
|
||||
width="8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-294.88332)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="0.52916664"
|
||||
cy="295.94165"
|
||||
cx="1.0583333"
|
||||
id="path4544"
|
||||
style="fill:#FFFFFF;fill-opacity:1;stroke:none;stroke-width:0.06614584;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
55
media/stream.svg
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 2.1166666 2.1166667"
|
||||
height="8"
|
||||
width="8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-294.88332)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="0.39687499"
|
||||
cy="295.94165"
|
||||
cx="1.0582843"
|
||||
id="path4544-3"
|
||||
style="fill:#00D29A;fill-opacity:1;stroke:none;stroke-width:0.13229169;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
d="M 0.41577997,296.5497 A 0.79374999,0.79374999 0 0 1 0.13224262,295.94165 0.79374999,0.79374999 0 0 1 0.41577996,295.3336"
|
||||
id="path4546-7"
|
||||
style="fill:none;fill-opacity:1;stroke:#00D29A;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
transform="scale(-1,1)"
|
||||
d="m -1.7008867,296.5497 a 0.79374999,0.79374999 0 0 1 -0.2835374,-0.60805 0.79374999,0.79374999 0 0 1 0.2835374,-0.60805"
|
||||
id="path4546-1-3"
|
||||
style="fill:none;fill-opacity:1;stroke:#00D29A;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
d="M 0.63239977,296.41847 A 0.62244385,0.62244385 0 0 1 0.4100551,295.94165 0.62244385,0.62244385 0 0 1 0.63239977,295.46483"
|
||||
id="path4546-5-7"
|
||||
style="fill:none;fill-opacity:1;stroke:#00D29A;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
transform="scale(-1,1)"
|
||||
d="m -1.484193,296.41847 a 0.62244391,0.62244391 0 0 1 -0.2223447,-0.47682 0.62244391,0.62244391 0 0 1 0.2223447,-0.47682"
|
||||
id="path4546-5-4-2"
|
||||
style="fill:none;fill-opacity:1;stroke:#00D29A;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
55
media/streaming.svg
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 2.1166666 2.1166667"
|
||||
height="8"
|
||||
width="8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-294.88332)"
|
||||
id="layer1">
|
||||
<circle
|
||||
r="0.39687499"
|
||||
cy="295.94165"
|
||||
cx="1.0582843"
|
||||
id="path4544-3"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.13229169;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
d="M 0.41577997,296.5497 A 0.79374999,0.79374999 0 0 1 0.13224262,295.94165 0.79374999,0.79374999 0 0 1 0.41577996,295.3336"
|
||||
id="path4546-7"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
transform="scale(-1,1)"
|
||||
d="m -1.7008867,296.5497 a 0.79374999,0.79374999 0 0 1 -0.2835374,-0.60805 0.79374999,0.79374999 0 0 1 0.2835374,-0.60805"
|
||||
id="path4546-1-3"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
d="M 0.63239977,296.41847 A 0.62244385,0.62244385 0 0 1 0.4100551,295.94165 0.62244385,0.62244385 0 0 1 0.63239977,295.46483"
|
||||
id="path4546-5-7"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
<path
|
||||
transform="scale(-1,1)"
|
||||
d="m -1.484193,296.41847 a 0.62244391,0.62244391 0 0 1 -0.2223447,-0.47682 0.62244391,0.62244391 0 0 1 0.2223447,-0.47682"
|
||||
id="path4546-5-4-2"
|
||||
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.15875001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
4
media/virtual_cam_off.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 3.011719 1 c -1.652344 0 -3.011719 1.359375 -3.011719 3.011719 v 4.976562 c 0 1.652344 1.359375 3.011719 3.011719 3.011719 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 c -0.578125 0 -1.011719 -0.433594 -1.011719 -1.011719 v -4.976562 c 0 -0.578125 0.433594 -1.011719 1.011719 -1.011719 h 7.976562 c 0.578125 0 1.011719 0.433594 1.011719 1.011719 v 1.953125 c 0 0.554687 0.449219 1 1 1 s 1 -0.445313 1 -1 v -1.953125 c 0 -1.652344 -1.359375 -3.011719 -3.011719 -3.011719 z m 3.917969 7.011719 c -1.070313 0 -1.929688 0.863281 -1.929688 1.929687 v 3.140625 c 0 1.070313 0.859375 1.929688 1.929688 1.929688 h 4.140624 c 1.070313 0 1.929688 -0.859375 1.929688 -1.929688 v -0.578125 l 1.851562 1.378906 c 0.214844 0.160157 0.5 0.183594 0.738282 0.066407 c 0.242187 -0.121094 0.390625 -0.367188 0.390625 -0.636719 v -3.601562 c 0.003906 -0.269532 -0.148438 -0.515626 -0.390625 -0.636719 c -0.238282 -0.121094 -0.527344 -0.09375 -0.742188 0.066406 l -1.847656 1.371094 v -0.570313 c 0 -1.066406 -0.859375 -1.929687 -1.929688 -1.929687 z m 0 0" fill="#C08000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
media/virtual_cam_on.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m 3.011719 1 c -1.652344 0 -3.011719 1.359375 -3.011719 3.011719 v 4.976562 c 0 1.652344 1.359375 3.011719 3.011719 3.011719 c 0.550781 0 1 -0.449219 1 -1 s -0.449219 -1 -1 -1 c -0.578125 0 -1.011719 -0.433594 -1.011719 -1.011719 v -4.976562 c 0 -0.578125 0.433594 -1.011719 1.011719 -1.011719 h 7.976562 c 0.578125 0 1.011719 0.433594 1.011719 1.011719 v 1.953125 c 0 0.554687 0.449219 1 1 1 s 1 -0.445313 1 -1 v -1.953125 c 0 -1.652344 -1.359375 -3.011719 -3.011719 -3.011719 z m 3.917969 7.011719 c -1.070313 0 -1.929688 0.863281 -1.929688 1.929687 v 3.140625 c 0 1.070313 0.859375 1.929688 1.929688 1.929688 h 4.140624 c 1.070313 0 1.929688 -0.859375 1.929688 -1.929688 v -0.578125 l 1.851562 1.378906 c 0.214844 0.160157 0.5 0.183594 0.738282 0.066407 c 0.242187 -0.121094 0.390625 -0.367188 0.390625 -0.636719 v -3.601562 c 0.003906 -0.269532 -0.148438 -0.515626 -0.390625 -0.636719 c -0.238282 -0.121094 -0.527344 -0.09375 -0.742188 0.066406 l -1.847656 1.371094 v -0.570313 c 0 -1.066406 -0.859375 -1.929687 -1.929688 -1.929687 z m 0 0" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
198
multi-canvas-source.c
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
|
||||
#include <obs-module.h>
|
||||
#include "multi-canvas-source.h"
|
||||
|
||||
struct multi_canvas_info {
|
||||
obs_source_t *source;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
DARRAY(obs_canvas_t *) canvas;
|
||||
DARRAY(uint32_t) widths;
|
||||
DARRAY(uint32_t) heights;
|
||||
DARRAY(gs_texrender_t *) renders;
|
||||
};
|
||||
|
||||
const char *multi_canvas_get_name(void *type_data)
|
||||
{
|
||||
UNUSED_PARAMETER(type_data);
|
||||
return "vertical_multi_canvas";
|
||||
}
|
||||
|
||||
void *multi_canvas_create(obs_data_t *settings, obs_source_t *source)
|
||||
{
|
||||
UNUSED_PARAMETER(settings);
|
||||
struct multi_canvas_info *multi_canvas = bzalloc(sizeof(struct multi_canvas_info));
|
||||
multi_canvas->source = source;
|
||||
return multi_canvas;
|
||||
}
|
||||
|
||||
void multi_canvas_destroy(void *data)
|
||||
{
|
||||
struct multi_canvas_info *mc = data;
|
||||
da_free(mc->canvas);
|
||||
da_free(mc->widths);
|
||||
da_free(mc->heights);
|
||||
for (size_t i = 0; i < mc->renders.num; i++) {
|
||||
gs_texrender_destroy(mc->renders.array[i]);
|
||||
}
|
||||
da_free(mc->renders);
|
||||
bfree(data);
|
||||
}
|
||||
|
||||
bool view_get_width(void *data, struct obs_video_info *ovi)
|
||||
{
|
||||
uint32_t *width = data;
|
||||
if (ovi->base_width > *width)
|
||||
*width = ovi->base_width;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool view_get_height(void *data, struct obs_video_info *ovi)
|
||||
{
|
||||
uint32_t *height = data;
|
||||
if (ovi->base_height > *height)
|
||||
*height = ovi->base_height;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void multi_canvas_video_render(void *data, gs_effect_t *effect)
|
||||
{
|
||||
struct multi_canvas_info *mc = data;
|
||||
gs_matrix_push();
|
||||
for (uint32_t i = 0; i < MAX_CHANNELS; i++) {
|
||||
obs_source_t *s = obs_get_output_source(i);
|
||||
if (!s)
|
||||
continue;
|
||||
if (obs_source_removed(s)) {
|
||||
obs_source_release(s);
|
||||
continue;
|
||||
}
|
||||
gs_matrix_push();
|
||||
gs_blend_state_push();
|
||||
obs_source_video_render(s);
|
||||
gs_blend_state_pop();
|
||||
gs_matrix_pop();
|
||||
obs_source_release(s);
|
||||
}
|
||||
|
||||
struct obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
gs_matrix_translate3f((float)ovi.base_width, 0.0f, 0.0f);
|
||||
|
||||
effect = obs_get_base_effect(OBS_EFFECT_DEFAULT);
|
||||
|
||||
for (size_t i = 0; i < mc->canvas.num; i++) {
|
||||
obs_canvas_t *canvas = mc->canvas.array[i];
|
||||
const enum gs_color_format format = gs_get_format_from_space(gs_get_color_space());
|
||||
if (gs_texrender_get_format(mc->renders.array[i]) != format) {
|
||||
gs_texrender_destroy(mc->renders.array[i]);
|
||||
mc->renders.array[i] = gs_texrender_create(format, GS_ZS_NONE);
|
||||
}
|
||||
|
||||
gs_texrender_reset(mc->renders.array[i]);
|
||||
gs_blend_state_push();
|
||||
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
|
||||
if (gs_texrender_begin_with_color_space(mc->renders.array[i], mc->widths.array[i], mc->heights.array[i],
|
||||
gs_get_color_space())) {
|
||||
|
||||
struct vec4 clear_color;
|
||||
|
||||
vec4_zero(&clear_color);
|
||||
gs_clear(GS_CLEAR_COLOR, &clear_color, 0.0f, 0);
|
||||
gs_ortho(0.0f, (float)mc->widths.array[i], 0.0f, (float)mc->heights.array[i], -100.0f, 100.0f);
|
||||
|
||||
obs_canvas_render(canvas);
|
||||
|
||||
gs_texrender_end(mc->renders.array[i]);
|
||||
}
|
||||
gs_blend_state_pop();
|
||||
|
||||
gs_texture_t *tex = gs_texrender_get_texture(mc->renders.array[i]);
|
||||
if (tex) {
|
||||
const bool previous = gs_framebuffer_srgb_enabled();
|
||||
gs_enable_framebuffer_srgb(true);
|
||||
|
||||
gs_effect_set_texture_srgb(gs_effect_get_param_by_name(effect, "image"), tex);
|
||||
|
||||
while (gs_effect_loop(effect, "Draw"))
|
||||
gs_draw_sprite(tex, 0, mc->widths.array[i], mc->heights.array[i]);
|
||||
|
||||
gs_enable_framebuffer_srgb(previous);
|
||||
}
|
||||
|
||||
gs_matrix_translate3f((float)mc->widths.array[i], 0.0f, 0.0f);
|
||||
}
|
||||
gs_matrix_pop();
|
||||
}
|
||||
|
||||
uint32_t multi_canvas_get_width(void *data)
|
||||
{
|
||||
struct multi_canvas_info *mc = data;
|
||||
return mc->width;
|
||||
}
|
||||
|
||||
uint32_t multi_canvas_get_height(void *data)
|
||||
{
|
||||
struct multi_canvas_info *mc = data;
|
||||
return mc->height;
|
||||
}
|
||||
|
||||
void multi_canvas_update_size(struct multi_canvas_info *mc)
|
||||
{
|
||||
struct obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
uint32_t width = ovi.base_width;
|
||||
uint32_t height = ovi.base_height;
|
||||
|
||||
for (size_t i = 0; i < mc->widths.num; i++) {
|
||||
width += mc->widths.array[i];
|
||||
if (mc->heights.array[i] > height)
|
||||
height = mc->heights.array[i];
|
||||
}
|
||||
mc->width = width;
|
||||
mc->height = height;
|
||||
}
|
||||
|
||||
void multi_canvas_source_add_canvas(void *data, obs_canvas_t *canvas, uint32_t width, uint32_t height)
|
||||
{
|
||||
struct multi_canvas_info *mc = data;
|
||||
for (size_t i = 0; i < mc->canvas.num; i++) {
|
||||
if (mc->canvas.array[i] == canvas)
|
||||
return;
|
||||
}
|
||||
da_push_back(mc->widths, &width);
|
||||
da_push_back(mc->heights, &height);
|
||||
da_push_back(mc->canvas, &canvas);
|
||||
gs_texrender_t *render = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||
da_push_back(mc->renders, &render);
|
||||
|
||||
multi_canvas_update_size(mc);
|
||||
}
|
||||
|
||||
void multi_canvas_source_remove_canvas(void *data, obs_canvas_t *canvas)
|
||||
{
|
||||
struct multi_canvas_info *mc = data;
|
||||
for (size_t i = 0; i < mc->canvas.num; i++) {
|
||||
if (mc->canvas.array[i] == canvas) {
|
||||
gs_texrender_destroy(mc->renders.array[i]);
|
||||
da_erase(mc->canvas, i);
|
||||
da_erase(mc->widths, i);
|
||||
da_erase(mc->heights, i);
|
||||
da_erase(mc->renders, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
multi_canvas_update_size(mc);
|
||||
}
|
||||
|
||||
struct obs_source_info multi_canvas_source = {
|
||||
.id = "vertical_multi_canvas_source",
|
||||
.type = OBS_SOURCE_TYPE_INPUT,
|
||||
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CAP_DISABLED | OBS_SOURCE_CUSTOM_DRAW,
|
||||
.get_name = multi_canvas_get_name,
|
||||
.create = multi_canvas_create,
|
||||
.destroy = multi_canvas_destroy,
|
||||
.video_render = multi_canvas_video_render,
|
||||
.get_width = multi_canvas_get_width,
|
||||
.get_height = multi_canvas_get_height,
|
||||
};
|
||||
16
multi-canvas-source.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include <util/darray.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void multi_canvas_source_add_canvas(void *data, obs_canvas_t *canvas, uint32_t width, uint32_t height);
|
||||
void multi_canvas_source_remove_canvas(void *data, obs_canvas_t *canvas);
|
||||
|
||||
extern struct obs_source_info multi_canvas_source;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
53
name-dialog.cpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#include "name-dialog.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "obs-module.h"
|
||||
|
||||
NameDialog::NameDialog(QWidget *parent, const QString &title) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(title);
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
setMinimumWidth(200);
|
||||
setMinimumHeight(100);
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
setLayout(layout);
|
||||
|
||||
userText = new QLineEdit(this);
|
||||
layout->addWidget(userText);
|
||||
|
||||
QDialogButtonBox *buttonbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttonbox);
|
||||
buttonbox->setCenterButtons(true);
|
||||
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
static bool IsWhitespace(char ch)
|
||||
{
|
||||
return ch == ' ' || ch == '\t';
|
||||
}
|
||||
|
||||
static void CleanWhitespace(std::string &str)
|
||||
{
|
||||
while (!str.empty() && IsWhitespace(str.back()))
|
||||
str.erase(str.end() - 1);
|
||||
while (!str.empty() && IsWhitespace(str.front()))
|
||||
str.erase(str.begin());
|
||||
}
|
||||
|
||||
bool NameDialog::AskForName(QWidget *parent, const QString &title, std::string &name)
|
||||
{
|
||||
NameDialog dialog(parent, title);
|
||||
dialog.userText->setMaxLength(170);
|
||||
dialog.userText->setText(QString::fromUtf8(name.c_str()));
|
||||
dialog.userText->selectAll();
|
||||
|
||||
if (dialog.exec() != DialogCode::Accepted) {
|
||||
return false;
|
||||
}
|
||||
name = dialog.userText->text().toUtf8().constData();
|
||||
CleanWhitespace(name);
|
||||
return true;
|
||||
}
|
||||
17
name-dialog.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QDialogButtonBox>
|
||||
#include <string>
|
||||
|
||||
class NameDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static bool AskForName(QWidget *parent, const QString &title, std::string &name);
|
||||
|
||||
private:
|
||||
NameDialog(QWidget *parent, const QString &title);
|
||||
QLineEdit *userText;
|
||||
};
|
||||
216
obs-websocket-api.h
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2022 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _OBS_WEBSOCKET_API_H
|
||||
#define _OBS_WEBSOCKET_API_H
|
||||
|
||||
#include <obs.h>
|
||||
|
||||
#define OBS_WEBSOCKET_API_VERSION 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *obs_websocket_vendor;
|
||||
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
|
||||
|
||||
struct obs_websocket_request_response {
|
||||
unsigned int status_code;
|
||||
char *comment;
|
||||
char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API.
|
||||
};
|
||||
|
||||
/* ==================== INTERNAL DEFINITIONS ==================== */
|
||||
|
||||
struct obs_websocket_request_callback {
|
||||
obs_websocket_request_callback_function callback;
|
||||
void *priv_data;
|
||||
};
|
||||
|
||||
inline proc_handler_t *_ph;
|
||||
|
||||
/* ==================== INTERNAL API FUNCTIONS ==================== */
|
||||
|
||||
static inline proc_handler_t *obs_websocket_get_ph(void)
|
||||
{
|
||||
proc_handler_t *global_ph = obs_get_proc_handler();
|
||||
assert(global_ph != NULL);
|
||||
|
||||
calldata_t cd = {0};
|
||||
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
|
||||
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
|
||||
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_ensure_ph(void)
|
||||
{
|
||||
if (!_ph)
|
||||
_ph = obs_websocket_get_ph();
|
||||
return _ph != NULL;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return false;
|
||||
|
||||
if (!vendor || !proc_name || !strlen(proc_name) || !cd)
|
||||
return false;
|
||||
|
||||
calldata_set_ptr(cd, "vendor", vendor);
|
||||
|
||||
proc_handler_call(_ph, proc_name, cd);
|
||||
return calldata_bool(cd, "success");
|
||||
}
|
||||
|
||||
/* ==================== GENERAL API FUNCTIONS ==================== */
|
||||
|
||||
// Gets the API version built with the obs-websocket plugin
|
||||
static inline unsigned int obs_websocket_get_api_version(void)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return 0;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
if (!proc_handler_call(_ph, "get_api_version", &cd))
|
||||
return 1; // API v1 does not include get_api_version
|
||||
|
||||
unsigned int ret = (unsigned int)calldata_int(&cd, "version");
|
||||
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
|
||||
static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return NULL;
|
||||
|
||||
const char *request_data_string = NULL;
|
||||
if (request_data)
|
||||
request_data_string = obs_data_get_json(request_data);
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "request_type", request_type);
|
||||
calldata_set_string(&cd, "request_data", request_data_string);
|
||||
|
||||
proc_handler_call(_ph, "call_request", &cd);
|
||||
|
||||
auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
|
||||
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Free a request response object returned by `obs_websocket_call_request()`
|
||||
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
|
||||
{
|
||||
if (!response)
|
||||
return;
|
||||
|
||||
if (response->comment)
|
||||
bfree(response->comment);
|
||||
if (response->response_data)
|
||||
bfree(response->response_data);
|
||||
bfree(response);
|
||||
}
|
||||
|
||||
/* ==================== VENDOR API FUNCTIONS ==================== */
|
||||
|
||||
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
|
||||
// Registers a new "vendor" (Example: obs-ndi)
|
||||
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return NULL;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "name", vendor_name);
|
||||
|
||||
proc_handler_call(_ph, "vendor_register", &cd);
|
||||
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Registers a new request for a vendor
|
||||
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
|
||||
obs_websocket_request_callback_function request_callback, void *priv_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
struct obs_websocket_request_callback cb = {};
|
||||
cb.callback = request_callback;
|
||||
cb.priv_data = priv_data;
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
calldata_set_ptr(&cd, "callback", &cb);
|
||||
|
||||
const bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Unregisters an existing vendor request
|
||||
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
|
||||
const bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Does not affect event_data refcount.
|
||||
// Emits an event under the vendor's name
|
||||
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", event_name);
|
||||
calldata_set_ptr(&cd, "data", (void *)event_data);
|
||||
|
||||
const bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/* ==================== END API FUNCTIONS ==================== */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
435
projector.cpp
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
#include <QAction>
|
||||
#include <QGuiApplication>
|
||||
#include <QMouseEvent>
|
||||
#include <QMenu>
|
||||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include "projector.hpp"
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
#include <util/platform.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
static inline void GetScaleAndCenterPos(int baseCX, int baseCY, int windowCX, int windowCY, int &x, int &y, float &scale)
|
||||
{
|
||||
double windowAspect, baseAspect;
|
||||
int newCX, newCY;
|
||||
|
||||
windowAspect = double(windowCX) / double(windowCY);
|
||||
baseAspect = double(baseCX) / double(baseCY);
|
||||
|
||||
if (windowAspect > baseAspect) {
|
||||
scale = float(windowCY) / float(baseCY);
|
||||
newCX = int(double(windowCY) * baseAspect);
|
||||
newCY = windowCY;
|
||||
} else {
|
||||
scale = float(windowCX) / float(baseCX);
|
||||
newCX = windowCX;
|
||||
newCY = int(float(windowCX) / baseAspect);
|
||||
}
|
||||
|
||||
x = windowCX / 2 - newCX / 2;
|
||||
y = windowCY / 2 - newCY / 2;
|
||||
}
|
||||
|
||||
static inline void startRegion(int vX, int vY, int vCX, int vCY, float oL, float oR, float oT, float oB)
|
||||
{
|
||||
gs_projection_push();
|
||||
gs_viewport_push();
|
||||
gs_set_viewport(vX, vY, vCX, vCY);
|
||||
gs_ortho(oL, oR, oT, oB, -100.0f, 100.0f);
|
||||
}
|
||||
|
||||
static inline void endRegion()
|
||||
{
|
||||
gs_viewport_pop();
|
||||
gs_projection_pop();
|
||||
}
|
||||
|
||||
config_t *get_user_config(void);
|
||||
|
||||
OBSProjector::OBSProjector(CanvasDock *canvas_, int monitor) : OBSQTDisplay(nullptr, Qt::Window), canvas(canvas_)
|
||||
{
|
||||
isAlwaysOnTop = config_get_bool(get_user_config(), "BasicWindow", "ProjectorAlwaysOnTop");
|
||||
|
||||
if (isAlwaysOnTop)
|
||||
setWindowFlags(Qt::WindowStaysOnTopHint);
|
||||
|
||||
// Mark the window as a projector so SetDisplayAffinity
|
||||
// can skip it
|
||||
windowHandle()->setProperty("isOBSProjectorWindow", true);
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__)
|
||||
// Prevents resizing of projector windows
|
||||
setAttribute(Qt::WA_PaintOnScreen, false);
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs_256x256.png")));
|
||||
#else
|
||||
setWindowIcon(QIcon::fromTheme("obs", QIcon(":/res/images/obs.png")));
|
||||
#endif
|
||||
|
||||
if (monitor == -1)
|
||||
resize(480, 270);
|
||||
else
|
||||
SetMonitor(monitor);
|
||||
|
||||
UpdateProjectorTitle();
|
||||
|
||||
QAction *action = new QAction(this);
|
||||
action->setShortcut(Qt::Key_Escape);
|
||||
addAction(action);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(EscapeTriggered()));
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
|
||||
//disable application quit when last window closed
|
||||
setAttribute(Qt::WA_QuitOnClose, false);
|
||||
|
||||
//installEventFilter(CreateShortcutFilter());
|
||||
|
||||
auto addDrawCallback = [this]() {
|
||||
obs_display_add_draw_callback(GetDisplay(), OBSRender, this);
|
||||
obs_display_set_background_color(GetDisplay(), 0x000000);
|
||||
};
|
||||
|
||||
connect(this, &OBSQTDisplay::DisplayCreated, addDrawCallback);
|
||||
//connect(App(), &QGuiApplication::screenRemoved, this, &OBSProjector::ScreenRemoved);
|
||||
|
||||
//App()->IncrementSleepInhibition();
|
||||
|
||||
ready = true;
|
||||
|
||||
show();
|
||||
|
||||
// We need it here to allow keyboard input in X11 to listen to Escape
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
OBSProjector::~OBSProjector()
|
||||
{
|
||||
obs_display_remove_draw_callback(GetDisplay(), OBSRender, this);
|
||||
|
||||
//App()->DecrementSleepInhibition();
|
||||
|
||||
screen = nullptr;
|
||||
}
|
||||
|
||||
void OBSProjector::SetMonitor(int monitor)
|
||||
{
|
||||
savedMonitor = monitor;
|
||||
auto screens = QGuiApplication::screens();
|
||||
if (monitor < 0 || monitor >= screens.size())
|
||||
screen = nullptr;
|
||||
else
|
||||
screen = screens[monitor];
|
||||
if (screen)
|
||||
setGeometry(screen->geometry());
|
||||
showFullScreen();
|
||||
SetHideCursor();
|
||||
}
|
||||
|
||||
void OBSProjector::SetHideCursor()
|
||||
{
|
||||
if (savedMonitor == -1)
|
||||
return;
|
||||
|
||||
bool hideCursor = config_get_bool(get_user_config(), "BasicWindow", "HideProjectorCursor");
|
||||
|
||||
if (hideCursor)
|
||||
setCursor(Qt::BlankCursor);
|
||||
else
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
|
||||
void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
|
||||
{
|
||||
OBSProjector *window = reinterpret_cast<OBSProjector *>(data);
|
||||
|
||||
if (!window->ready)
|
||||
return;
|
||||
|
||||
obs_canvas_t *canvas = window->canvas->canvas;
|
||||
|
||||
uint32_t targetCX;
|
||||
uint32_t targetCY;
|
||||
int x, y;
|
||||
int newCX, newCY;
|
||||
float scale;
|
||||
|
||||
targetCX = window->canvas->canvas_width;
|
||||
targetCY = window->canvas->canvas_height;
|
||||
|
||||
GetScaleAndCenterPos(targetCX, targetCY, cx, cy, x, y, scale);
|
||||
|
||||
newCX = int(scale * float(targetCX));
|
||||
newCY = int(scale * float(targetCY));
|
||||
|
||||
startRegion(x, y, newCX, newCY, 0.0f, float(targetCX), 0.0f, float(targetCY));
|
||||
|
||||
obs_canvas_render(canvas);
|
||||
|
||||
endRegion();
|
||||
}
|
||||
|
||||
void OBSProjector::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
OBSQTDisplay::mousePressEvent(event);
|
||||
|
||||
if (event->button() == Qt::RightButton) {
|
||||
QMenu popup(this);
|
||||
|
||||
QMenu *projectorMenu = new QMenu(QString::fromUtf8(obs_frontend_get_locale_string("Fullscreen")));
|
||||
canvas->AddProjectorMenuMonitors(projectorMenu, this, SLOT(OpenFullScreenProjector()));
|
||||
popup.addMenu(projectorMenu);
|
||||
|
||||
if (GetMonitor() > -1) {
|
||||
popup.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Windowed")), this,
|
||||
SLOT(OpenWindowedProjector()));
|
||||
|
||||
} else if (!this->isMaximized()) {
|
||||
popup.addAction(QString::fromUtf8(obs_frontend_get_locale_string("ResizeProjectorWindowToContent")), this,
|
||||
SLOT(ResizeToContent()));
|
||||
}
|
||||
|
||||
QAction *alwaysOnTopButton =
|
||||
new QAction(QString::fromUtf8(obs_frontend_get_locale_string("Basic.MainMenu.View.AlwaysOnTop")), this);
|
||||
alwaysOnTopButton->setCheckable(true);
|
||||
alwaysOnTopButton->setChecked(isAlwaysOnTop);
|
||||
|
||||
connect(alwaysOnTopButton, &QAction::toggled, this, &OBSProjector::AlwaysOnTopToggled);
|
||||
|
||||
popup.addAction(alwaysOnTopButton);
|
||||
|
||||
popup.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Close")), this, SLOT(EscapeTriggered()));
|
||||
popup.exec(QCursor::pos());
|
||||
}
|
||||
}
|
||||
|
||||
void OBSProjector::EscapeTriggered()
|
||||
{
|
||||
canvas->DeleteProjector(this);
|
||||
}
|
||||
|
||||
void OBSProjector::UpdateProjectorTitle(QString name)
|
||||
{
|
||||
UNUSED_PARAMETER(name);
|
||||
bool window = (GetMonitor() == -1);
|
||||
|
||||
QString title = QString::fromUtf8(obs_frontend_get_locale_string(window ? "PreviewWindow" : "PreviewProjector"));
|
||||
|
||||
setWindowTitle(title);
|
||||
}
|
||||
|
||||
int OBSProjector::GetMonitor()
|
||||
{
|
||||
return savedMonitor;
|
||||
}
|
||||
|
||||
void OBSProjector::RenameProjector(QString oldName, QString newName)
|
||||
{
|
||||
if (oldName == newName)
|
||||
return;
|
||||
|
||||
UpdateProjectorTitle(newName);
|
||||
}
|
||||
|
||||
void OBSProjector::OpenFullScreenProjector()
|
||||
{
|
||||
if (!isFullScreen())
|
||||
prevGeometry = geometry();
|
||||
|
||||
int monitor = sender()->property("monitor").toInt();
|
||||
SetMonitor(monitor);
|
||||
|
||||
UpdateProjectorTitle();
|
||||
}
|
||||
|
||||
void OBSProjector::OpenWindowedProjector()
|
||||
{
|
||||
showFullScreen();
|
||||
showNormal();
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
||||
if (!prevGeometry.isNull())
|
||||
setGeometry(prevGeometry);
|
||||
else
|
||||
resize(480, 270);
|
||||
|
||||
savedMonitor = -1;
|
||||
|
||||
UpdateProjectorTitle();
|
||||
screen = nullptr;
|
||||
}
|
||||
|
||||
void OBSProjector::ResizeToContent()
|
||||
{
|
||||
uint32_t targetCX = canvas->canvas_width;
|
||||
uint32_t targetCY = canvas->canvas_height;
|
||||
int x, y, newX, newY;
|
||||
float scale;
|
||||
|
||||
QSize size = this->size();
|
||||
GetScaleAndCenterPos(targetCX, targetCY, size.width(), size.height(), x, y, scale);
|
||||
|
||||
newX = size.width() - (x * 2);
|
||||
newY = size.height() - (y * 2);
|
||||
resize(newX, newY);
|
||||
}
|
||||
|
||||
void OBSProjector::AlwaysOnTopToggled(bool onTop)
|
||||
{
|
||||
SetIsAlwaysOnTop(onTop, true);
|
||||
}
|
||||
|
||||
void OBSProjector::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
EscapeTriggered();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
bool OBSProjector::IsAlwaysOnTop() const
|
||||
{
|
||||
return isAlwaysOnTop;
|
||||
}
|
||||
|
||||
bool OBSProjector::IsAlwaysOnTopOverridden() const
|
||||
{
|
||||
return isAlwaysOnTopOverridden;
|
||||
}
|
||||
|
||||
void OBSProjector::SetIsAlwaysOnTop(bool onTop, bool isOverridden)
|
||||
{
|
||||
isAlwaysOnTop = onTop;
|
||||
isAlwaysOnTopOverridden = isOverridden;
|
||||
|
||||
SetAlwaysOnTop(this, onTop);
|
||||
}
|
||||
|
||||
void OBSProjector::ScreenRemoved(QScreen *screen_)
|
||||
{
|
||||
if (GetMonitor() < 0 || !screen)
|
||||
return;
|
||||
|
||||
if (screen == screen_)
|
||||
EscapeTriggered();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
bool IsAlwaysOnTop(QWidget *window)
|
||||
{
|
||||
DWORD exStyle = GetWindowLong((HWND)window->winId(), GWL_EXSTYLE);
|
||||
return (exStyle & WS_EX_TOPMOST) != 0;
|
||||
}
|
||||
#else
|
||||
bool IsAlwaysOnTop(QWidget *window)
|
||||
{
|
||||
return (window->windowFlags() & Qt::WindowStaysOnTopHint) != 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
void SetAlwaysOnTop(QWidget *window, bool enable)
|
||||
{
|
||||
HWND hwnd = (HWND)window->winId();
|
||||
SetWindowPos(hwnd, enable ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||
}
|
||||
#else
|
||||
void SetAlwaysOnTop(QWidget *window, bool enable)
|
||||
{
|
||||
Qt::WindowFlags flags = window->windowFlags();
|
||||
|
||||
if (enable) {
|
||||
flags |= Qt::WindowStaysOnTopHint;
|
||||
} else {
|
||||
flags &= ~Qt::WindowStaysOnTopHint;
|
||||
}
|
||||
|
||||
window->setWindowFlags(flags);
|
||||
window->show();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 4, 0)
|
||||
#define GENERIC_MONITOR_NAME QStringLiteral("Generic PnP Monitor")
|
||||
|
||||
struct MonitorData {
|
||||
const wchar_t *id;
|
||||
MONITORINFOEX info;
|
||||
bool found;
|
||||
};
|
||||
|
||||
static BOOL CALLBACK GetMonitorCallback(HMONITOR monitor, HDC, LPRECT, LPARAM param)
|
||||
{
|
||||
MonitorData *data = (MonitorData *)param;
|
||||
|
||||
if (GetMonitorInfoW(monitor, &data->info)) {
|
||||
if (wcscmp(data->info.szDevice, data->id) == 0) {
|
||||
data->found = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString GetMonitorName(const QString &id)
|
||||
{
|
||||
MonitorData data = {};
|
||||
data.id = (const wchar_t *)id.utf16();
|
||||
data.info.cbSize = sizeof(data.info);
|
||||
|
||||
EnumDisplayMonitors(nullptr, nullptr, GetMonitorCallback, (LPARAM)&data);
|
||||
if (!data.found) {
|
||||
return GENERIC_MONITOR_NAME;
|
||||
}
|
||||
|
||||
UINT32 numPath, numMode;
|
||||
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPath, &numMode) != ERROR_SUCCESS) {
|
||||
return GENERIC_MONITOR_NAME;
|
||||
}
|
||||
|
||||
std::vector<DISPLAYCONFIG_PATH_INFO> paths(numPath);
|
||||
std::vector<DISPLAYCONFIG_MODE_INFO> modes(numMode);
|
||||
|
||||
if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &numPath, paths.data(), &numMode, modes.data(), nullptr) != ERROR_SUCCESS) {
|
||||
return GENERIC_MONITOR_NAME;
|
||||
}
|
||||
|
||||
DISPLAYCONFIG_TARGET_DEVICE_NAME target;
|
||||
bool found = false;
|
||||
|
||||
paths.resize(numPath);
|
||||
for (size_t i = 0; i < numPath; ++i) {
|
||||
const DISPLAYCONFIG_PATH_INFO &path = paths[i];
|
||||
|
||||
DISPLAYCONFIG_SOURCE_DEVICE_NAME s;
|
||||
s.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
|
||||
s.header.size = sizeof(s);
|
||||
s.header.adapterId = path.sourceInfo.adapterId;
|
||||
s.header.id = path.sourceInfo.id;
|
||||
|
||||
if (DisplayConfigGetDeviceInfo(&s.header) == ERROR_SUCCESS &&
|
||||
wcscmp(data.info.szDevice, s.viewGdiDeviceName) == 0) {
|
||||
target.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
|
||||
target.header.size = sizeof(target);
|
||||
target.header.adapterId = path.sourceInfo.adapterId;
|
||||
target.header.id = path.targetInfo.id;
|
||||
found = DisplayConfigGetDeviceInfo(&target.header) == ERROR_SUCCESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return GENERIC_MONITOR_NAME;
|
||||
}
|
||||
|
||||
return QString::fromWCharArray(target.monitorFriendlyDeviceName);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
53
projector.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include <obs.hpp>
|
||||
#include "qt-display.hpp"
|
||||
#include "vertical-canvas.hpp"
|
||||
|
||||
bool IsAlwaysOnTop(QWidget *window);
|
||||
void SetAlwaysOnTop(QWidget *window, bool enable);
|
||||
|
||||
class OBSProjector : public OBSQTDisplay {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
CanvasDock *canvas = nullptr;
|
||||
|
||||
static void OBSRender(void *data, uint32_t cx, uint32_t cy);
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
bool isAlwaysOnTop;
|
||||
bool isAlwaysOnTopOverridden = false;
|
||||
int savedMonitor = -1;
|
||||
|
||||
bool ready = false;
|
||||
|
||||
void UpdateProjectorTitle(QString name = "");
|
||||
|
||||
QRect prevGeometry;
|
||||
void SetMonitor(int monitor);
|
||||
|
||||
QScreen *screen = nullptr;
|
||||
|
||||
private slots:
|
||||
void EscapeTriggered();
|
||||
void OpenFullScreenProjector();
|
||||
void ResizeToContent();
|
||||
void OpenWindowedProjector();
|
||||
void AlwaysOnTopToggled(bool alwaysOnTop);
|
||||
void ScreenRemoved(QScreen *screen_);
|
||||
|
||||
public:
|
||||
OBSProjector(CanvasDock *canvas_, int monitor);
|
||||
~OBSProjector();
|
||||
|
||||
int GetMonitor();
|
||||
void RenameProjector(QString oldName, QString newName);
|
||||
void SetHideCursor();
|
||||
|
||||
bool IsAlwaysOnTop() const;
|
||||
bool IsAlwaysOnTopOverridden() const;
|
||||
void SetIsAlwaysOnTop(bool isAlwaysOnTop, bool isOverridden);
|
||||
};
|
||||
260
qt-display.cpp
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
#include "qt-display.hpp"
|
||||
#include "display-helpers.hpp"
|
||||
#include <QWindow>
|
||||
#include <QScreen>
|
||||
#include <QResizeEvent>
|
||||
#include <QShowEvent>
|
||||
|
||||
#include <obs-config.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#include <obs-nix-platform.h>
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_WAYLAND
|
||||
#include <QGuiApplication>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class SurfaceEventFilter : public QObject {
|
||||
OBSQTDisplay *display;
|
||||
|
||||
public:
|
||||
SurfaceEventFilter(OBSQTDisplay *src) : QObject(src), display(src) {}
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override
|
||||
{
|
||||
bool result = QObject::eventFilter(obj, event);
|
||||
QPlatformSurfaceEvent *surfaceEvent;
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::PlatformSurface:
|
||||
surfaceEvent = static_cast<QPlatformSurfaceEvent *>(event);
|
||||
|
||||
switch (surfaceEvent->surfaceEventType()) {
|
||||
case QPlatformSurfaceEvent::SurfaceCreated:
|
||||
display->RestoreDisplay();
|
||||
break;
|
||||
case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed:
|
||||
display->DestroyDisplay();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case QEvent::Expose:
|
||||
display->RestoreDisplay();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
static inline long long color_to_int(const QColor &color)
|
||||
{
|
||||
auto shift = [&](unsigned val, int shift_left) {
|
||||
return ((val & 0xff) << shift_left);
|
||||
};
|
||||
|
||||
return shift(color.red(), 0) | shift(color.green(), 8) | shift(color.blue(), 16) | shift(color.alpha(), 24);
|
||||
}
|
||||
|
||||
static inline QColor rgba_to_color(uint32_t rgba)
|
||||
{
|
||||
return QColor::fromRgb(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF, (rgba >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
OBSQTDisplay::OBSQTDisplay(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags)
|
||||
{
|
||||
setAttribute(Qt::WA_PaintOnScreen);
|
||||
setAttribute(Qt::WA_StaticContents);
|
||||
setAttribute(Qt::WA_NoSystemBackground);
|
||||
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
setAttribute(Qt::WA_DontCreateNativeAncestors);
|
||||
setAttribute(Qt::WA_NativeWindow);
|
||||
|
||||
auto windowVisible = [this](bool visible) {
|
||||
if (!visible) {
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
display = nullptr;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (!display) {
|
||||
CreateDisplay();
|
||||
} else {
|
||||
QSize size = GetPixelSize(this);
|
||||
obs_display_resize(display, size.width(), size.height());
|
||||
}
|
||||
};
|
||||
|
||||
auto screenChanged = [this](QScreen *) {
|
||||
CreateDisplay();
|
||||
|
||||
QSize size = GetPixelSize(this);
|
||||
obs_display_resize(display, size.width(), size.height());
|
||||
};
|
||||
|
||||
connect(windowHandle(), &QWindow::visibleChanged, windowVisible);
|
||||
connect(windowHandle(), &QWindow::screenChanged, screenChanged);
|
||||
|
||||
windowHandle()->installEventFilter(new SurfaceEventFilter(this));
|
||||
}
|
||||
|
||||
QColor OBSQTDisplay::GetDisplayBackgroundColor() const
|
||||
{
|
||||
return rgba_to_color(backgroundColor);
|
||||
}
|
||||
|
||||
void OBSQTDisplay::SetDisplayBackgroundColor(const QColor &color)
|
||||
{
|
||||
uint32_t newBackgroundColor = (uint32_t)color_to_int(color);
|
||||
|
||||
if (newBackgroundColor != backgroundColor) {
|
||||
backgroundColor = newBackgroundColor;
|
||||
UpdateDisplayBackgroundColor();
|
||||
}
|
||||
}
|
||||
|
||||
void OBSQTDisplay::UpdateDisplayBackgroundColor()
|
||||
{
|
||||
obs_display_set_background_color(display, backgroundColor);
|
||||
}
|
||||
|
||||
static bool QTToGSWindow(QWindow *window, gs_window &gswindow)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
#ifdef _WIN32
|
||||
gswindow.hwnd = (HWND)window->winId();
|
||||
#elif __APPLE__
|
||||
gswindow.view = (id)window->winId();
|
||||
#else
|
||||
switch (obs_get_nix_platform()) {
|
||||
case OBS_NIX_PLATFORM_X11_EGL:
|
||||
gswindow.id = (uint32_t)window->winId();
|
||||
gswindow.display = obs_get_nix_platform_display();
|
||||
break;
|
||||
#ifdef ENABLE_WAYLAND
|
||||
case OBS_NIX_PLATFORM_WAYLAND: {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
||||
QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface();
|
||||
gswindow.display = native->nativeResourceForWindow("surface", window);
|
||||
#else
|
||||
gswindow.display = (void *)window->winId();
|
||||
#endif
|
||||
success = gswindow.display != nullptr;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
void OBSQTDisplay::CreateDisplay()
|
||||
{
|
||||
if (display)
|
||||
return;
|
||||
|
||||
if (destroying)
|
||||
return;
|
||||
|
||||
if (!windowHandle()->isExposed())
|
||||
return;
|
||||
|
||||
QSize size = GetPixelSize(this);
|
||||
|
||||
gs_init_data info = {};
|
||||
info.cx = size.width();
|
||||
info.cy = size.height();
|
||||
info.format = GS_BGRA;
|
||||
info.zsformat = GS_ZS_NONE;
|
||||
|
||||
if (!QTToGSWindow(windowHandle(), info.window))
|
||||
return;
|
||||
|
||||
display = obs_display_create(&info, backgroundColor);
|
||||
|
||||
emit DisplayCreated(this);
|
||||
}
|
||||
|
||||
void OBSQTDisplay::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
CreateDisplay();
|
||||
|
||||
QWidget::paintEvent(event);
|
||||
}
|
||||
|
||||
void OBSQTDisplay::moveEvent(QMoveEvent *event)
|
||||
{
|
||||
QWidget::moveEvent(event);
|
||||
|
||||
OnMove();
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
bool OBSQTDisplay::nativeEvent(const QByteArray &, void *message, qintptr *)
|
||||
#else
|
||||
bool OBSQTDisplay::nativeEvent(const QByteArray &, void *message, long *)
|
||||
#endif
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const MSG &msg = *static_cast<MSG *>(message);
|
||||
switch (msg.message) {
|
||||
case WM_DISPLAYCHANGE:
|
||||
OnDisplayChange();
|
||||
}
|
||||
#else
|
||||
UNUSED_PARAMETER(message);
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void OBSQTDisplay::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
CreateDisplay();
|
||||
|
||||
if (isVisible() && display) {
|
||||
QSize size = GetPixelSize(this);
|
||||
obs_display_resize(display, size.width(), size.height());
|
||||
}
|
||||
|
||||
emit DisplayResized();
|
||||
}
|
||||
|
||||
QPaintEngine *OBSQTDisplay::paintEngine() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void OBSQTDisplay::OnMove()
|
||||
{
|
||||
if (display)
|
||||
obs_display_update_color_space(display);
|
||||
}
|
||||
|
||||
void OBSQTDisplay::OnDisplayChange()
|
||||
{
|
||||
if (display)
|
||||
obs_display_update_color_space(display);
|
||||
}
|
||||
56
qt-display.hpp
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <obs.hpp>
|
||||
|
||||
#define GREY_COLOR_BACKGROUND 0xFF4C4C4C
|
||||
|
||||
class OBSQTDisplay : public QWidget {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(
|
||||
QColor displayBackgroundColor MEMBER backgroundColor READ GetDisplayBackgroundColor WRITE SetDisplayBackgroundColor)
|
||||
|
||||
OBSDisplay display;
|
||||
bool destroying = false;
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
virtual void moveEvent(QMoveEvent *event) override;
|
||||
virtual void resizeEvent(QResizeEvent *event) override;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
|
||||
#else
|
||||
virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void DisplayCreated(OBSQTDisplay *window);
|
||||
void DisplayResized();
|
||||
|
||||
public:
|
||||
OBSQTDisplay(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
|
||||
~OBSQTDisplay() { display = nullptr; }
|
||||
|
||||
virtual QPaintEngine *paintEngine() const override;
|
||||
|
||||
inline obs_display_t *GetDisplay() const { return display; }
|
||||
|
||||
uint32_t backgroundColor = GREY_COLOR_BACKGROUND;
|
||||
|
||||
QColor GetDisplayBackgroundColor() const;
|
||||
void SetDisplayBackgroundColor(const QColor &color);
|
||||
void UpdateDisplayBackgroundColor();
|
||||
void CreateDisplay();
|
||||
void DestroyDisplay()
|
||||
{
|
||||
display = nullptr;
|
||||
destroying = true;
|
||||
};
|
||||
void RestoreDisplay()
|
||||
{
|
||||
destroying = false;
|
||||
CreateDisplay();
|
||||
};
|
||||
|
||||
void OnMove();
|
||||
void OnDisplayChange();
|
||||
};
|
||||
32
resource.rc.in
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
1 VERSIONINFO
|
||||
FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0
|
||||
PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0
|
||||
FILEFLAGSMASK 0x0L
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x0L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Aitum"
|
||||
VALUE "FileDescription", "${PROJECT_FULL_NAME}"
|
||||
VALUE "FileVersion", "${PROJECT_VERSION}"
|
||||
VALUE "InternalName", "${PROJECT_NAME}"
|
||||
VALUE "LegalCopyright", "(C) Aitum"
|
||||
VALUE "OriginalFilename", "${PROJECT_NAME}"
|
||||
VALUE "ProductName", "${PROJECT_FULL_NAME}"
|
||||
VALUE "ProductVersion", "${PROJECT_VERSION}"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200
|
||||
END
|
||||
END
|
||||
13
resources.qrc
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<RCC>
|
||||
<qresource prefix="/aitum">
|
||||
<file>media/aitum.png</file>
|
||||
<file>media/record.svg</file>
|
||||
<file>media/recording.svg</file>
|
||||
<file>media/stream.svg</file>
|
||||
<file>media/streaming.svg</file>
|
||||
<file>media/backtrack_off.svg</file>
|
||||
<file>media/backtrack_on.svg</file>
|
||||
<file>media/virtual_cam_off.svg</file>
|
||||
<file>media/virtual_cam_on.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
409
scenes-dock.cpp
Normal file
|
|
@ -0,0 +1,409 @@
|
|||
|
||||
|
||||
#include "scenes-dock.hpp"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QToolBar>
|
||||
#include <QWidgetAction>
|
||||
|
||||
#include "name-dialog.hpp"
|
||||
#include "obs-module.h"
|
||||
#include "vertical-canvas.hpp"
|
||||
|
||||
void CanvasScenesDock::SetGridMode(bool checked)
|
||||
{
|
||||
if (checked) {
|
||||
sceneList->setResizeMode(QListView::Adjust);
|
||||
sceneList->setViewMode(QListView::IconMode);
|
||||
sceneList->setUniformItemSizes(true);
|
||||
sceneList->setStyleSheet("*{padding: 0; margin: 0;}");
|
||||
} else {
|
||||
sceneList->setViewMode(QListView::ListMode);
|
||||
sceneList->setResizeMode(QListView::Fixed);
|
||||
sceneList->setStyleSheet("");
|
||||
}
|
||||
}
|
||||
|
||||
bool CanvasScenesDock::IsGridMode()
|
||||
{
|
||||
return sceneList->viewMode() == QListView::IconMode;
|
||||
}
|
||||
|
||||
void CanvasScenesDock::ShowScenesContextMenu(QListWidgetItem *widget_item)
|
||||
{
|
||||
auto menu = QMenu(this);
|
||||
auto a = menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Main.GridMode")),
|
||||
[this](bool checked) { SetGridMode(checked); });
|
||||
a->setCheckable(true);
|
||||
a->setChecked(IsGridMode());
|
||||
menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Add")), [this] { canvasDock->AddScene(); });
|
||||
if (!widget_item) {
|
||||
menu.exec(QCursor::pos());
|
||||
return;
|
||||
}
|
||||
menu.addSeparator();
|
||||
menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Duplicate")), [this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
canvasDock->AddScene(item->text());
|
||||
});
|
||||
menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Remove")), [this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
canvasDock->RemoveScene(item->text());
|
||||
});
|
||||
menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Rename")), [this] {
|
||||
const auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
std::string name = item->text().toUtf8().constData();
|
||||
obs_source_t *source = obs_canvas_get_source_by_name(canvasDock->canvas, name.c_str());
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
obs_source_t *s = nullptr;
|
||||
do {
|
||||
obs_source_release(s);
|
||||
if (!NameDialog::AskForName(this, QString::fromUtf8(obs_module_text("SceneName")), name)) {
|
||||
break;
|
||||
}
|
||||
s = obs_canvas_get_source_by_name(canvasDock->canvas, name.c_str());
|
||||
if (s) {
|
||||
continue;
|
||||
}
|
||||
obs_source_set_name(source, name.c_str());
|
||||
} while (s);
|
||||
obs_source_release(source);
|
||||
});
|
||||
auto orderMenu = menu.addMenu(QString::fromUtf8(obs_frontend_get_locale_string("Basic.MainMenu.Edit.Order")));
|
||||
orderMenu->addAction(QString::fromUtf8(obs_frontend_get_locale_string("Basic.MainMenu.Edit.Order.MoveUp")),
|
||||
[this] { ChangeSceneIndex(true, -1, 0); });
|
||||
orderMenu->addAction(QString::fromUtf8(obs_frontend_get_locale_string("Basic.MainMenu.Edit.Order.MoveDown")),
|
||||
[this] { ChangeSceneIndex(true, 1, sceneList->count() - 1); });
|
||||
orderMenu->addAction(QString::fromUtf8(obs_frontend_get_locale_string("Basic.MainMenu.Edit.Order.MoveToTop")),
|
||||
[this] { ChangeSceneIndex(false, 0, 0); });
|
||||
orderMenu->addAction(QString::fromUtf8(obs_frontend_get_locale_string("Basic.MainMenu.Edit.Order.MoveToBottom")),
|
||||
[this] { ChangeSceneIndex(false, 1, sceneList->count() - 1); });
|
||||
|
||||
menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Screenshot.Scene")), [this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto s = obs_canvas_get_source_by_name(canvasDock->canvas, item->text().toUtf8().constData());
|
||||
if (s) {
|
||||
obs_frontend_take_source_screenshot(s);
|
||||
obs_source_release(s);
|
||||
}
|
||||
});
|
||||
menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Filters")), [this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto s = obs_canvas_get_source_by_name(canvasDock->canvas, item->text().toUtf8().constData());
|
||||
if (s) {
|
||||
obs_frontend_open_source_filters(s);
|
||||
obs_source_release(s);
|
||||
}
|
||||
});
|
||||
|
||||
auto tom = menu.addMenu(QString::fromUtf8(obs_frontend_get_locale_string("TransitionOverride")));
|
||||
std::string scene_name = widget_item->text().toUtf8().constData();
|
||||
OBSSourceAutoRelease scene_source = obs_canvas_get_source_by_name(canvasDock->canvas, scene_name.c_str());
|
||||
OBSDataAutoRelease private_settings = obs_source_get_private_settings(scene_source);
|
||||
obs_data_set_default_int(private_settings, "transition_duration", 300);
|
||||
const char *curTransition = obs_data_get_string(private_settings, "transition");
|
||||
int curDuration = (int)obs_data_get_int(private_settings, "transition_duration");
|
||||
|
||||
QSpinBox *duration = new QSpinBox(tom);
|
||||
duration->setMinimum(50);
|
||||
duration->setSuffix(" ms");
|
||||
duration->setMaximum(20000);
|
||||
duration->setSingleStep(50);
|
||||
duration->setValue(curDuration);
|
||||
|
||||
connect(duration, (void (QSpinBox::*)(int))&QSpinBox::valueChanged, [this, scene_name](int dur) {
|
||||
OBSSourceAutoRelease source = obs_canvas_get_source_by_name(canvasDock->canvas, scene_name.c_str());
|
||||
OBSDataAutoRelease ps = obs_source_get_private_settings(source);
|
||||
|
||||
obs_data_set_int(ps, "transition_duration", dur);
|
||||
});
|
||||
|
||||
auto action = tom->addAction(QString::fromUtf8(obs_frontend_get_locale_string("None")));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(!curTransition || !strlen(curTransition));
|
||||
connect(action, &QAction::triggered, [this, scene_name] {
|
||||
OBSSourceAutoRelease source = obs_canvas_get_source_by_name(canvasDock->canvas, scene_name.c_str());
|
||||
OBSDataAutoRelease ps = obs_source_get_private_settings(source);
|
||||
obs_data_set_string(ps, "transition", "");
|
||||
});
|
||||
|
||||
for (auto t : canvasDock->transitions) {
|
||||
const char *name = obs_source_get_name(t);
|
||||
bool match = (name && curTransition && strcmp(name, curTransition) == 0);
|
||||
|
||||
if (!name || !*name) {
|
||||
name = obs_frontend_get_locale_string("None");
|
||||
}
|
||||
|
||||
auto a2 = tom->addAction(QString::fromUtf8(name));
|
||||
a2->setCheckable(true);
|
||||
a2->setChecked(match);
|
||||
connect(a2, &QAction::triggered, [this, scene_name, a2] {
|
||||
OBSSourceAutoRelease source = obs_canvas_get_source_by_name(canvasDock->canvas, scene_name.c_str());
|
||||
OBSDataAutoRelease ps = obs_source_get_private_settings(source);
|
||||
obs_data_set_string(ps, "transition", a2->text().toUtf8().constData());
|
||||
});
|
||||
}
|
||||
|
||||
QWidgetAction *durationAction = new QWidgetAction(tom);
|
||||
durationAction->setDefaultWidget(duration);
|
||||
|
||||
tom->addSeparator();
|
||||
tom->addAction(durationAction);
|
||||
|
||||
auto linkedScenesMenu = menu.addMenu(QString::fromUtf8(obs_module_text("LinkedScenes")));
|
||||
connect(linkedScenesMenu, &QMenu::aboutToShow, [linkedScenesMenu, this] {
|
||||
linkedScenesMenu->clear();
|
||||
struct obs_frontend_source_list scenes = {};
|
||||
obs_frontend_get_scenes(&scenes);
|
||||
for (size_t i = 0; i < scenes.sources.num; i++) {
|
||||
obs_source_t *src = scenes.sources.array[i];
|
||||
obs_data_t *settings = obs_source_get_settings(src);
|
||||
|
||||
auto name = QString::fromUtf8(obs_source_get_name(src));
|
||||
auto *checkBox = new QCheckBox(name, linkedScenesMenu);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(checkBox, &QCheckBox::checkStateChanged, [this, src, checkBox] {
|
||||
#else
|
||||
connect(checkBox, &QCheckBox::stateChanged, [this, src, checkBox] {
|
||||
#endif
|
||||
canvasDock->SetLinkedScene(src, checkBox->isChecked() ? sceneList->currentItem()->text() : "");
|
||||
});
|
||||
auto *checkableAction = new QWidgetAction(linkedScenesMenu);
|
||||
checkableAction->setDefaultWidget(checkBox);
|
||||
linkedScenesMenu->addAction(checkableAction);
|
||||
|
||||
auto c = obs_data_get_array(settings, "canvas");
|
||||
if (c) {
|
||||
const auto count = obs_data_array_count(c);
|
||||
|
||||
for (size_t j = 0; j < count; j++) {
|
||||
auto item = obs_data_array_item(c, j);
|
||||
if (!item)
|
||||
continue;
|
||||
if (obs_data_get_int(item, "width") == canvasDock->canvas_width &&
|
||||
obs_data_get_int(item, "height") == canvasDock->canvas_height) {
|
||||
auto sn = QString::fromUtf8(obs_data_get_string(item, "scene"));
|
||||
if (sn == sceneList->currentItem()->text()) {
|
||||
checkBox->setChecked(true);
|
||||
}
|
||||
}
|
||||
obs_data_release(item);
|
||||
}
|
||||
|
||||
obs_data_array_release(c);
|
||||
}
|
||||
|
||||
obs_data_release(settings);
|
||||
}
|
||||
obs_frontend_source_list_free(&scenes);
|
||||
});
|
||||
|
||||
menu.addAction(QString::fromUtf8(obs_module_text("OnMainCanvas")), [this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto s = obs_canvas_get_source_by_name(canvasDock->canvas, item->text().toUtf8().constData());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (obs_frontend_preview_program_mode_active()) {
|
||||
obs_frontend_set_current_preview_scene(s);
|
||||
} else {
|
||||
obs_frontend_set_current_scene(s);
|
||||
}
|
||||
obs_source_release(s);
|
||||
});
|
||||
|
||||
a = menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("ShowInMultiview")), [this, scene_name](bool checked) {
|
||||
OBSSourceAutoRelease source = obs_canvas_get_source_by_name(canvasDock->canvas, scene_name.c_str());
|
||||
OBSDataAutoRelease ps = obs_source_get_private_settings(source);
|
||||
obs_data_set_bool(ps, "show_in_multiview", checked);
|
||||
});
|
||||
a->setCheckable(true);
|
||||
obs_data_set_default_bool(private_settings, "show_in_multiview", true);
|
||||
a->setChecked(obs_data_get_bool(private_settings, "show_in_multiview"));
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
CanvasScenesDock::CanvasScenesDock(CanvasDock *canvas_dock, QWidget *parent) : QFrame(parent), canvasDock(canvas_dock)
|
||||
{
|
||||
setMinimumWidth(100);
|
||||
setMinimumHeight(50);
|
||||
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
sceneList = new QListWidget();
|
||||
sceneList->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
sceneList->setFrameShape(QFrame::NoFrame);
|
||||
sceneList->setFrameShadow(QFrame::Plain);
|
||||
sceneList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
sceneList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(sceneList, &QListWidget::customContextMenuRequested,
|
||||
[this](const QPoint &pos) { ShowScenesContextMenu(sceneList->itemAt(pos)); });
|
||||
|
||||
connect(sceneList, &QListWidget::currentItemChanged, [this]() {
|
||||
const auto item = sceneList->currentItem();
|
||||
if (!item || canvasDock->clearing || canvasDock->switching) {
|
||||
return;
|
||||
}
|
||||
canvasDock->SwitchScene(item->text());
|
||||
if (!item->isSelected()) {
|
||||
item->setSelected(true);
|
||||
}
|
||||
});
|
||||
connect(sceneList, &QListWidget::itemSelectionChanged, [this] {
|
||||
const auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
if (!item->isSelected()) {
|
||||
item->setSelected(true);
|
||||
}
|
||||
});
|
||||
|
||||
QAction *renameAction = new QAction(sceneList);
|
||||
#ifdef __APPLE__
|
||||
renameAction->setShortcut({Qt::Key_Return});
|
||||
#else
|
||||
renameAction->setShortcut({Qt::Key_F2});
|
||||
#endif
|
||||
renameAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||
connect(renameAction, &QAction::triggered, [this]() {
|
||||
const auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
obs_source_t *source = obs_canvas_get_source_by_name(canvasDock->canvas, item->text().toUtf8().constData());
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
std::string name = obs_source_get_name(source);
|
||||
obs_source_t *s = nullptr;
|
||||
do {
|
||||
obs_source_release(s);
|
||||
if (!NameDialog::AskForName(this, QString::fromUtf8(obs_module_text("SceneName")), name)) {
|
||||
break;
|
||||
}
|
||||
s = obs_canvas_get_source_by_name(canvasDock->canvas, name.c_str());
|
||||
if (s) {
|
||||
continue;
|
||||
}
|
||||
obs_source_set_name(source, name.c_str());
|
||||
} while (s);
|
||||
obs_source_release(source);
|
||||
});
|
||||
sceneList->addAction(renameAction);
|
||||
|
||||
mainLayout->addWidget(sceneList, 1);
|
||||
|
||||
auto toolbar = new QToolBar();
|
||||
toolbar->setObjectName(QStringLiteral("scenesToolbar"));
|
||||
toolbar->setIconSize(QSize(16, 16));
|
||||
toolbar->setFloatable(false);
|
||||
auto a = toolbar->addAction(QIcon(QString::fromUtf8(":/res/images/plus.svg")),
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("Add")), [this] { canvasDock->AddScene(); });
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("addIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-plus");
|
||||
|
||||
a = toolbar->addAction(QIcon(":/res/images/minus.svg"), QString::fromUtf8(obs_frontend_get_locale_string("RemoveScene")),
|
||||
[this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
canvasDock->RemoveScene(item->text());
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-minus");
|
||||
a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||
#ifdef __APPLE__
|
||||
a->setShortcut({Qt::Key_Backspace});
|
||||
#else
|
||||
a->setShortcut({Qt::Key_Delete});
|
||||
#endif
|
||||
sceneList->addAction(a);
|
||||
toolbar->addSeparator();
|
||||
a = toolbar->addAction(
|
||||
QIcon(":/res/images/filter.svg"), QString::fromUtf8(obs_frontend_get_locale_string("SceneFilters")), [this] {
|
||||
auto item = sceneList->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto s = obs_canvas_get_source_by_name(canvasDock->canvas, item->text().toUtf8().constData());
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
obs_frontend_open_source_filters(s);
|
||||
obs_source_release(s);
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("filtersIcon")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-filter");
|
||||
toolbar->addSeparator();
|
||||
a = toolbar->addAction(QIcon(":/res/images/up.svg"), QString::fromUtf8(obs_frontend_get_locale_string("MoveSceneUp")),
|
||||
[this] { ChangeSceneIndex(true, -1, 0); });
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-up");
|
||||
a = toolbar->addAction(QIcon(":/res/images/down.svg"), QString::fromUtf8(obs_frontend_get_locale_string("MoveSceneDown")),
|
||||
[this] { ChangeSceneIndex(true, 1, sceneList->count() - 1); });
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-down");
|
||||
mainLayout->addWidget(toolbar, 0);
|
||||
|
||||
setObjectName(QStringLiteral("contextContainer"));
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void CanvasScenesDock::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
|
||||
{
|
||||
int idx = sceneList->currentRow();
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto canvasItem = sceneList->item(idx);
|
||||
if (!canvasItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (idx == invalidIdx) {
|
||||
return;
|
||||
}
|
||||
|
||||
sceneList->blockSignals(true);
|
||||
auto item = sceneList->takeItem(idx);
|
||||
if (relative) {
|
||||
sceneList->insertItem(idx + offset, item);
|
||||
sceneList->setCurrentRow(idx + offset);
|
||||
} else if (offset == 0) {
|
||||
sceneList->insertItem(offset, item);
|
||||
} else {
|
||||
sceneList->insertItem(sceneList->count(), item);
|
||||
}
|
||||
item->setSelected(true);
|
||||
sceneList->blockSignals(false);
|
||||
}
|
||||
|
||||
CanvasScenesDock::~CanvasScenesDock() {}
|
||||
25
scenes-dock.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QListWidget>
|
||||
#include <QWidget>
|
||||
|
||||
class CanvasDock;
|
||||
|
||||
class CanvasScenesDock : public QFrame {
|
||||
Q_OBJECT
|
||||
friend class CanvasDock;
|
||||
|
||||
private:
|
||||
QListWidget *sceneList;
|
||||
CanvasDock *canvasDock;
|
||||
|
||||
void ChangeSceneIndex(bool relative, int offset, int invalidIdx);
|
||||
void ShowScenesContextMenu(QListWidgetItem *item);
|
||||
void SetGridMode(bool checked);
|
||||
bool IsGridMode();
|
||||
|
||||
public:
|
||||
CanvasScenesDock(CanvasDock *canvas_dock, QWidget *parent = nullptr);
|
||||
~CanvasScenesDock();
|
||||
};
|
||||
1662
source-tree.cpp
Normal file
225
source-tree.hpp
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QPointer>
|
||||
#include <QListView>
|
||||
#include <QCheckBox>
|
||||
#include <QStaticText>
|
||||
#include <QAbstractListModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "vertical-canvas.hpp"
|
||||
|
||||
class QLabel;
|
||||
class QCheckBox;
|
||||
class QLineEdit;
|
||||
class SourceTree;
|
||||
class QSpacerItem;
|
||||
class QHBoxLayout;
|
||||
class LockedCheckBox;
|
||||
class VisibilityCheckBox;
|
||||
class VisibilityItemWidget;
|
||||
|
||||
class SourceTreeSubItemCheckBox : public QCheckBox {
|
||||
Q_OBJECT
|
||||
};
|
||||
|
||||
class SourceTreeItem : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
friend class SourceTree;
|
||||
friend class SourceTreeModel;
|
||||
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
virtual bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
void Update(bool force);
|
||||
|
||||
enum class Type {
|
||||
Unknown,
|
||||
Item,
|
||||
Group,
|
||||
SubItem,
|
||||
};
|
||||
|
||||
void DisconnectSignals();
|
||||
void ReconnectSignals();
|
||||
|
||||
Type type = Type::Unknown;
|
||||
|
||||
public:
|
||||
explicit SourceTreeItem(SourceTree *tree, OBSSceneItem sceneitem);
|
||||
~SourceTreeItem();
|
||||
bool IsEditing();
|
||||
|
||||
private:
|
||||
QSpacerItem *spacer = nullptr;
|
||||
QCheckBox *expand = nullptr;
|
||||
QLabel *iconLabel = nullptr;
|
||||
VisibilityCheckBox *vis = nullptr;
|
||||
LockedCheckBox *lock = nullptr;
|
||||
QHBoxLayout *boxLayout = nullptr;
|
||||
QLabel *label = nullptr;
|
||||
|
||||
QLineEdit *editor = nullptr;
|
||||
|
||||
std::string newName;
|
||||
|
||||
SourceTree *tree;
|
||||
OBSSceneItem sceneitem;
|
||||
std::vector<OBSSignal> sigs;
|
||||
static void removeScene(void *data, calldata_t *cd);
|
||||
static void removeItem(void *data, calldata_t *cd);
|
||||
static void itemVisible(void *data, calldata_t *cd);
|
||||
static void itemLocked(void *data, calldata_t *cd);
|
||||
static void itemSelect(void *data, calldata_t *cd);
|
||||
static void itemDeselect(void *data, calldata_t *cd);
|
||||
static void reorderGroup(void *data, calldata_t *);
|
||||
static void renamed(void *data, calldata_t *cd);
|
||||
static void removeSource(void *data, calldata_t *);
|
||||
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
void ExitEditModeInternal(bool save);
|
||||
|
||||
private slots:
|
||||
void Clear();
|
||||
|
||||
void EnterEditMode();
|
||||
void ExitEditMode(bool save);
|
||||
|
||||
void VisibilityChanged(bool visible);
|
||||
void LockedChanged(bool locked);
|
||||
void Renamed(const QString &name);
|
||||
|
||||
void ExpandClicked(bool checked);
|
||||
|
||||
void Select();
|
||||
void Deselect();
|
||||
};
|
||||
|
||||
class SourceTreeModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
friend class SourceTree;
|
||||
friend class SourceTreeItem;
|
||||
friend class CanvasDock;
|
||||
|
||||
SourceTree *st;
|
||||
QVector<OBSSceneItem> items;
|
||||
bool hasGroups = false;
|
||||
|
||||
static void OBSFrontendEvent(enum obs_frontend_event event, void *ptr);
|
||||
void Clear();
|
||||
void SceneChanged();
|
||||
void ReorderItems();
|
||||
|
||||
void Add(obs_sceneitem_t *item);
|
||||
void Remove(obs_sceneitem_t *item);
|
||||
OBSSceneItem Get(int idx);
|
||||
QString GetNewGroupName();
|
||||
void AddGroup();
|
||||
|
||||
void GroupSelectedItems(QModelIndexList &indices);
|
||||
void UngroupSelectedGroups(QModelIndexList &indices);
|
||||
|
||||
void ExpandGroup(obs_sceneitem_t *item);
|
||||
void CollapseGroup(obs_sceneitem_t *item);
|
||||
|
||||
void UpdateGroupState(bool update);
|
||||
|
||||
public:
|
||||
explicit SourceTreeModel(SourceTree *st);
|
||||
~SourceTreeModel();
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent) const override;
|
||||
virtual QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
virtual Qt::DropActions supportedDropActions() const override;
|
||||
};
|
||||
|
||||
class SourceTree : public QListView {
|
||||
Q_OBJECT
|
||||
|
||||
bool ignoreReorder = false;
|
||||
CanvasDock *canvasDock;
|
||||
|
||||
friend class SourceTreeModel;
|
||||
friend class SourceTreeItem;
|
||||
friend class CanvasDock;
|
||||
|
||||
bool textPrepared = false;
|
||||
QStaticText textNoSources;
|
||||
//QSvgRenderer iconNoSources;
|
||||
|
||||
bool iconsVisible = true;
|
||||
|
||||
void UpdateNoSourcesMessage();
|
||||
|
||||
void ResetWidgets();
|
||||
void UpdateWidget(const QModelIndex &idx, obs_sceneitem_t *item);
|
||||
void UpdateWidgets(bool force = false);
|
||||
|
||||
inline SourceTreeModel *GetStm() const { return reinterpret_cast<SourceTreeModel *>(model()); }
|
||||
|
||||
public:
|
||||
inline SourceTreeItem *GetItemWidget(int idx)
|
||||
{
|
||||
QWidget *widget = indexWidget(GetStm()->createIndex(idx, 0));
|
||||
return reinterpret_cast<SourceTreeItem *>(widget);
|
||||
}
|
||||
|
||||
explicit SourceTree(CanvasDock *canvas_dock, QWidget *parent = nullptr);
|
||||
|
||||
inline bool IgnoreReorder() const { return ignoreReorder; }
|
||||
inline void Clear() { GetStm()->Clear(); }
|
||||
|
||||
inline void Add(obs_sceneitem_t *item) { GetStm()->Add(item); }
|
||||
inline OBSSceneItem Get(int idx) { return GetStm()->Get(idx); }
|
||||
inline QString GetNewGroupName() { return GetStm()->GetNewGroupName(); }
|
||||
|
||||
void SelectItem(obs_sceneitem_t *sceneitem, bool select);
|
||||
|
||||
bool MultipleBaseSelected() const;
|
||||
bool GroupsSelected() const;
|
||||
bool GroupedItemsSelected() const;
|
||||
|
||||
void UpdateIcons();
|
||||
void SetIconsVisible(bool visible);
|
||||
|
||||
public slots:
|
||||
inline void ReorderItems() { GetStm()->ReorderItems(); }
|
||||
inline void RefreshItems() { GetStm()->SceneChanged(); }
|
||||
void Remove(OBSSceneItem item, OBSScene scene);
|
||||
void GroupSelectedItems();
|
||||
void UngroupSelectedGroups();
|
||||
void AddGroup();
|
||||
bool Edit(int idx);
|
||||
void NewGroupEdit(int idx);
|
||||
|
||||
protected:
|
||||
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
virtual void dropEvent(QDropEvent *event) override;
|
||||
virtual void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
|
||||
};
|
||||
|
||||
class SourceTreeDelegate : public QStyledItemDelegate {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SourceTreeDelegate(QObject *parent);
|
||||
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
};
|
||||
227
sources-dock.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
|
||||
|
||||
#include "sources-dock.hpp"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QToolBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "obs-module.h"
|
||||
#include "vertical-canvas.hpp"
|
||||
#include "name-dialog.hpp"
|
||||
|
||||
CanvasSourcesDock::CanvasSourcesDock(CanvasDock *canvas_dock, QWidget *parent) : QFrame(parent), canvasDock(canvas_dock)
|
||||
{
|
||||
setMinimumWidth(100);
|
||||
setMinimumHeight(50);
|
||||
|
||||
auto mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
sourceList = new SourceTree(canvas_dock, this);
|
||||
sourceList->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
|
||||
sourceList->setFrameShape(QFrame::NoFrame);
|
||||
sourceList->setFrameShadow(QFrame::Plain);
|
||||
sourceList->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
sourceList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
sourceList->setDropIndicatorShown(true);
|
||||
sourceList->setDragEnabled(true);
|
||||
sourceList->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
sourceList->setDefaultDropAction(Qt::TargetMoveAction);
|
||||
|
||||
connect(sourceList, &SourceTree::customContextMenuRequested, [this] { ShowSourcesContextMenu(GetCurrentSceneItem()); });
|
||||
|
||||
QAction *renameAction = new QAction(sourceList);
|
||||
#ifdef __APPLE__
|
||||
renameAction->setShortcut({Qt::Key_Return});
|
||||
#else
|
||||
renameAction->setShortcut({Qt::Key_F2});
|
||||
#endif
|
||||
renameAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||
connect(renameAction, &QAction::triggered, [this]() {
|
||||
obs_sceneitem_t *sceneItem = GetCurrentSceneItem();
|
||||
if (!sceneItem)
|
||||
return;
|
||||
obs_source_t *source = obs_source_get_ref(obs_sceneitem_get_source(sceneItem));
|
||||
if (!source)
|
||||
return;
|
||||
std::string name = obs_source_get_name(source);
|
||||
obs_source_t *s = nullptr;
|
||||
do {
|
||||
obs_source_release(s);
|
||||
if (!NameDialog::AskForName(this, QString::fromUtf8(obs_module_text("SourceName")), name)) {
|
||||
break;
|
||||
}
|
||||
s = obs_canvas_get_source_by_name(canvasDock->canvas, name.c_str());
|
||||
if (s)
|
||||
continue;
|
||||
obs_source_set_name(source, name.c_str());
|
||||
} while (s);
|
||||
obs_source_release(source);
|
||||
});
|
||||
sourceList->addAction(renameAction);
|
||||
|
||||
mainLayout->addWidget(sourceList, 1);
|
||||
|
||||
auto toolbar = new QToolBar();
|
||||
toolbar->setObjectName(QStringLiteral("scenesToolbar"));
|
||||
toolbar->setIconSize(QSize(16, 16));
|
||||
toolbar->setFloatable(false);
|
||||
auto a = toolbar->addAction(QIcon(QString::fromUtf8(":/res/images/plus.svg")),
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("AddSource")), [this] {
|
||||
const auto menu = canvasDock->CreateAddSourcePopupMenu();
|
||||
menu->exec(QCursor::pos());
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("addIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-plus");
|
||||
|
||||
a = toolbar->addAction(
|
||||
QIcon(":/res/images/minus.svg"), QString::fromUtf8(obs_frontend_get_locale_string("RemoveSource")), [this] {
|
||||
std::vector<OBSSceneItem> items;
|
||||
obs_scene_enum_items(canvasDock->scene, selected_items, &items);
|
||||
if (!items.size())
|
||||
return;
|
||||
/* ------------------------------------- */
|
||||
/* confirm action with user */
|
||||
|
||||
bool confirmed = false;
|
||||
|
||||
if (items.size() > 1) {
|
||||
QString text = QString::fromUtf8(obs_frontend_get_locale_string("ConfirmRemove.TextMultiple"))
|
||||
.arg(QString::number(items.size()));
|
||||
|
||||
QMessageBox remove_items(this);
|
||||
remove_items.setText(text);
|
||||
QPushButton *Yes = remove_items.addButton(QString::fromUtf8(obs_frontend_get_locale_string("Yes")),
|
||||
QMessageBox::YesRole);
|
||||
remove_items.setDefaultButton(Yes);
|
||||
remove_items.addButton(QString::fromUtf8(obs_frontend_get_locale_string("No")),
|
||||
QMessageBox::NoRole);
|
||||
remove_items.setIcon(QMessageBox::Question);
|
||||
remove_items.setWindowTitle(
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("ConfirmRemove.Title")));
|
||||
remove_items.exec();
|
||||
|
||||
confirmed = Yes == remove_items.clickedButton();
|
||||
} else {
|
||||
OBSSceneItem &item = items[0];
|
||||
obs_source_t *source = obs_sceneitem_get_source(item);
|
||||
if (source) {
|
||||
const char *name = obs_source_get_name(source);
|
||||
|
||||
QString text = QString::fromUtf8(obs_frontend_get_locale_string("ConfirmRemove.Text"))
|
||||
.arg(QString::fromUtf8(name));
|
||||
|
||||
QMessageBox remove_source(this);
|
||||
remove_source.setText(text);
|
||||
QPushButton *Yes = remove_source.addButton(
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("Yes")), QMessageBox::YesRole);
|
||||
remove_source.setDefaultButton(Yes);
|
||||
remove_source.addButton(QString::fromUtf8(obs_frontend_get_locale_string("No")),
|
||||
QMessageBox::NoRole);
|
||||
remove_source.setIcon(QMessageBox::Question);
|
||||
remove_source.setWindowTitle(
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("ConfirmRemove.Title")));
|
||||
remove_source.exec();
|
||||
confirmed = Yes == remove_source.clickedButton();
|
||||
}
|
||||
}
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
/* ----------------------------------------------- */
|
||||
/* remove items */
|
||||
|
||||
for (auto &item : items)
|
||||
obs_sceneitem_remove(item);
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-minus");
|
||||
a->setShortcutContext(Qt::WidgetWithChildrenShortcut);
|
||||
#ifdef __APPLE__
|
||||
a->setShortcut({Qt::Key_Backspace});
|
||||
#else
|
||||
a->setShortcut({Qt::Key_Delete});
|
||||
#endif
|
||||
sourceList->addAction(a);
|
||||
toolbar->addSeparator();
|
||||
a = toolbar->addAction(QIcon(":/res/images/filter.svg"), QString::fromUtf8(obs_frontend_get_locale_string("SourceFilters")),
|
||||
[this] {
|
||||
auto item = GetCurrentSceneItem();
|
||||
auto source = obs_sceneitem_get_source(item);
|
||||
if (source)
|
||||
obs_frontend_open_source_filters(source);
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("filtersIcon")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-filter");
|
||||
|
||||
a = toolbar->addAction(QIcon(":/settings/images/settings/general.svg"),
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("SourceProperties")), [this] {
|
||||
auto item = GetCurrentSceneItem();
|
||||
auto source = obs_sceneitem_get_source(item);
|
||||
if (source)
|
||||
obs_frontend_open_source_properties(source);
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("propertiesIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-gear");
|
||||
|
||||
toolbar->addSeparator();
|
||||
a = toolbar->addAction(QIcon(":/res/images/up.svg"), QString::fromUtf8(obs_frontend_get_locale_string("MoveSourceUp")),
|
||||
[this] {
|
||||
auto item = GetCurrentSceneItem();
|
||||
obs_sceneitem_set_order(item, OBS_ORDER_MOVE_UP);
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-up");
|
||||
a = toolbar->addAction(QIcon(":/res/images/down.svg"), QString::fromUtf8(obs_frontend_get_locale_string("MoveSourceDown")),
|
||||
[this] {
|
||||
auto item = GetCurrentSceneItem();
|
||||
obs_sceneitem_set_order(item, OBS_ORDER_MOVE_DOWN);
|
||||
});
|
||||
toolbar->widgetForAction(a)->setProperty("themeID", QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
toolbar->widgetForAction(a)->setProperty("class", "icon-down");
|
||||
mainLayout->addWidget(toolbar, 0);
|
||||
|
||||
setObjectName(QStringLiteral("contextContainer"));
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
CanvasSourcesDock::~CanvasSourcesDock() {}
|
||||
|
||||
void CanvasSourcesDock::ShowSourcesContextMenu(obs_sceneitem_t *item)
|
||||
{
|
||||
auto menu = QMenu(this);
|
||||
menu.addMenu(canvasDock->CreateAddSourcePopupMenu());
|
||||
if (item) {
|
||||
canvasDock->AddSceneItemMenuItems(&menu, item);
|
||||
}
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
bool CanvasSourcesDock::selected_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
|
||||
{
|
||||
std::vector<OBSSceneItem> &items = *reinterpret_cast<std::vector<OBSSceneItem> *>(param);
|
||||
|
||||
if (obs_sceneitem_selected(item)) {
|
||||
items.emplace_back(item);
|
||||
} else if (obs_sceneitem_is_group(item)) {
|
||||
obs_sceneitem_group_enum_items(item, selected_items, &items);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
obs_sceneitem_t *CanvasSourcesDock::GetCurrentSceneItem()
|
||||
{
|
||||
return sourceList->Get(GetTopSelectedSourceItem());
|
||||
}
|
||||
|
||||
int CanvasSourcesDock::GetTopSelectedSourceItem()
|
||||
{
|
||||
QModelIndexList selectedItems = sourceList->selectionModel()->selectedIndexes();
|
||||
return selectedItems.count() ? selectedItems[0].row() : -1;
|
||||
}
|
||||
28
sources-dock.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QListWidget>
|
||||
#include <QWidget>
|
||||
|
||||
#include "source-tree.hpp"
|
||||
|
||||
class CanvasDock;
|
||||
class SourceTree;
|
||||
|
||||
class CanvasSourcesDock : public QFrame {
|
||||
Q_OBJECT
|
||||
friend class CanvasDock;
|
||||
|
||||
private:
|
||||
SourceTree *sourceList;
|
||||
CanvasDock *canvasDock;
|
||||
|
||||
obs_sceneitem_t *GetCurrentSceneItem();
|
||||
int GetTopSelectedSourceItem();
|
||||
void ShowSourcesContextMenu(obs_sceneitem_t *item);
|
||||
static bool selected_items(obs_scene_t *, obs_sceneitem_t *item, void *param);
|
||||
|
||||
public:
|
||||
CanvasSourcesDock(CanvasDock *canvas_dock, QWidget *parent = nullptr);
|
||||
~CanvasSourcesDock();
|
||||
};
|
||||
243
transitions-dock.cpp
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#include "transitions-dock.hpp"
|
||||
|
||||
#include "obs-module.h"
|
||||
#include "vertical-canvas.hpp"
|
||||
#include <QMenu>
|
||||
#include "name-dialog.hpp"
|
||||
#include <QMessageBox>
|
||||
|
||||
CanvasTransitionsDock::CanvasTransitionsDock(CanvasDock *canvas_dock, QWidget *parent) : QFrame(parent), canvasDock(canvas_dock)
|
||||
{
|
||||
|
||||
setMinimumWidth(100);
|
||||
setMinimumHeight(50);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto mainLayout = new QVBoxLayout();
|
||||
mainLayout->setContentsMargins(4, 4, 4, 4);
|
||||
mainLayout->setSpacing(2);
|
||||
|
||||
transition = new QComboBox();
|
||||
mainLayout->addWidget(transition);
|
||||
|
||||
auto hl = new QHBoxLayout();
|
||||
/* auto l = new QLabel(QString::fromUtf8(
|
||||
obs_frontend_get_locale_string("Basic.TransitionDuration")));
|
||||
l->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
|
||||
hl->addWidget(l);
|
||||
duration = new QSpinBox();
|
||||
duration->setSuffix(" ms");
|
||||
duration->setMinimum(50);
|
||||
duration->setMaximum(20000);
|
||||
duration->setSingleStep(50);
|
||||
duration->setValue(300);
|
||||
l->setBuddy(duration);
|
||||
hl->addWidget(duration);
|
||||
mainLayout->addLayout(hl);
|
||||
|
||||
hl = new QHBoxLayout();*/
|
||||
hl->addStretch();
|
||||
auto addButton = new QPushButton();
|
||||
addButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
addButton->setAccessibleName(QString::fromUtf8(obs_frontend_get_locale_string("Basic.AddTransition")));
|
||||
addButton->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Basic.AddTransition")));
|
||||
addButton->setIcon(QIcon(":/res/images/add.png"));
|
||||
addButton->setProperty("themeID", "addIconSmall");
|
||||
addButton->setProperty("class", "icon-plus");
|
||||
addButton->setProperty("toolButton", true);
|
||||
addButton->setFlat(false);
|
||||
|
||||
connect(addButton, &QPushButton::clicked, [this] {
|
||||
auto menu = QMenu(this);
|
||||
auto subMenu = menu.addMenu(QString::fromUtf8(obs_module_text("CopyFromMain")));
|
||||
struct obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
for (size_t i = 0; i < transitions.sources.num; i++) {
|
||||
auto tr = transitions.sources.array[i];
|
||||
const char *name = obs_source_get_name(tr);
|
||||
auto action = subMenu->addAction(QString::fromUtf8(name));
|
||||
if (!obs_is_source_configurable(obs_source_get_unversioned_id(tr))) {
|
||||
action->setEnabled(false);
|
||||
}
|
||||
for (auto t : canvasDock->transitions) {
|
||||
if (strcmp(name, obs_source_get_name(t)) == 0) {
|
||||
action->setEnabled(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
connect(action, &QAction::triggered, [this, tr] {
|
||||
OBSDataAutoRelease d = obs_save_source(tr);
|
||||
OBSSourceAutoRelease t = obs_load_private_source(d);
|
||||
if (t) {
|
||||
canvasDock->transitions.emplace_back(t);
|
||||
auto n = QString::fromUtf8(obs_source_get_name(t));
|
||||
transition->addItem(n);
|
||||
transition->setCurrentText(n);
|
||||
}
|
||||
});
|
||||
}
|
||||
obs_frontend_source_list_free(&transitions);
|
||||
menu.addSeparator();
|
||||
size_t idx = 0;
|
||||
const char *id;
|
||||
while (obs_enum_transition_types(idx++, &id)) {
|
||||
if (!obs_is_source_configurable(id))
|
||||
continue;
|
||||
const char *display_name = obs_source_get_display_name(id);
|
||||
|
||||
auto action = menu.addAction(QString::fromUtf8(display_name));
|
||||
connect(action, &QAction::triggered, [this, id] {
|
||||
OBSSourceAutoRelease t = obs_source_create_private(id, obs_source_get_display_name(id), nullptr);
|
||||
if (t) {
|
||||
std::string name = obs_source_get_name(t);
|
||||
while (true) {
|
||||
if (!NameDialog::AskForName(
|
||||
this, QString::fromUtf8(obs_module_text("TransitionName")), name)) {
|
||||
obs_source_release(t);
|
||||
return;
|
||||
}
|
||||
if (name.empty())
|
||||
continue;
|
||||
bool found = false;
|
||||
for (auto tr : canvasDock->transitions) {
|
||||
if (strcmp(obs_source_get_name(tr), name.c_str()) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
obs_source_set_name(t, name.c_str());
|
||||
break;
|
||||
}
|
||||
canvasDock->transitions.emplace_back(t);
|
||||
auto n = QString::fromUtf8(obs_source_get_name(t));
|
||||
transition->addItem(n);
|
||||
transition->setCurrentText(n);
|
||||
obs_frontend_open_source_properties(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
menu.exec(QCursor::pos());
|
||||
});
|
||||
|
||||
hl->addWidget(addButton);
|
||||
|
||||
removeButton = new QPushButton();
|
||||
removeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
removeButton->setAccessibleName(QString::fromUtf8(obs_frontend_get_locale_string("Basic.RemoveTransition")));
|
||||
removeButton->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Basic.RemoveTransition")));
|
||||
removeButton->setIcon(QIcon(":/res/images/list_remove.png"));
|
||||
removeButton->setProperty("themeID", "removeIconSmall");
|
||||
removeButton->setProperty("class", "icon-minus");
|
||||
removeButton->setProperty("toolButton", true);
|
||||
removeButton->setFlat(false);
|
||||
|
||||
connect(removeButton, &QPushButton::clicked, [this] {
|
||||
QMessageBox mb(
|
||||
QMessageBox::Question, QString::fromUtf8(obs_frontend_get_locale_string("ConfirmRemove.Title")),
|
||||
QString::fromUtf8(obs_frontend_get_locale_string("ConfirmRemove.Text")).arg(transition->currentText()),
|
||||
QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No));
|
||||
mb.setDefaultButton(QMessageBox::NoButton);
|
||||
if (mb.exec() != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
auto n = transition->currentText().toUtf8();
|
||||
for (auto it = canvasDock->transitions.begin(); it != canvasDock->transitions.end(); ++it) {
|
||||
if (strcmp(n.constData(), obs_source_get_name(it->Get())) == 0) {
|
||||
if (!obs_is_source_configurable(obs_source_get_unversioned_id(it->Get())))
|
||||
return;
|
||||
canvasDock->transitions.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
transition->removeItem(transition->currentIndex());
|
||||
if (transition->currentIndex() < 0)
|
||||
transition->setCurrentIndex(0);
|
||||
});
|
||||
|
||||
hl->addWidget(removeButton);
|
||||
|
||||
propsButton = new QPushButton();
|
||||
propsButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
propsButton->setAccessibleName(QString::fromUtf8(obs_frontend_get_locale_string("Basic.TransitionProperties")));
|
||||
propsButton->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("Basic.TransitionProperties")));
|
||||
propsButton->setIcon(QIcon(":/settings/images/settings/general.svg"));
|
||||
propsButton->setProperty("themeID", "menuIconSmall");
|
||||
propsButton->setProperty("class", "icon-dots-vert");
|
||||
propsButton->setProperty("toolButton", true);
|
||||
propsButton->setFlat(false);
|
||||
|
||||
connect(propsButton, &QPushButton::clicked, [this] {
|
||||
auto menu = QMenu(this);
|
||||
auto action = menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Rename")));
|
||||
connect(action, &QAction::triggered, [this] {
|
||||
auto tn = transition->currentText().toUtf8();
|
||||
obs_source_t *t = canvasDock->GetTransition(tn.constData());
|
||||
if (!t)
|
||||
return;
|
||||
std::string name = obs_source_get_name(t);
|
||||
while (true) {
|
||||
if (!NameDialog::AskForName(this, QString::fromUtf8(obs_module_text("TransitionName")), name)) {
|
||||
return;
|
||||
}
|
||||
if (name.empty())
|
||||
continue;
|
||||
bool found = false;
|
||||
for (auto tr : canvasDock->transitions) {
|
||||
if (strcmp(obs_source_get_name(tr), name.c_str()) == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
transition->setItemText(transition->currentIndex(), QString::fromUtf8(name.c_str()));
|
||||
obs_source_set_name(t, name.c_str());
|
||||
break;
|
||||
}
|
||||
});
|
||||
action = menu.addAction(QString::fromUtf8(obs_frontend_get_locale_string("Properties")));
|
||||
connect(action, &QAction::triggered, [this] {
|
||||
auto tn = transition->currentText().toUtf8();
|
||||
auto t = canvasDock->GetTransition(tn.constData());
|
||||
if (!t)
|
||||
return;
|
||||
obs_frontend_open_source_properties(t);
|
||||
});
|
||||
menu.exec(QCursor::pos());
|
||||
});
|
||||
|
||||
hl->addWidget(propsButton);
|
||||
|
||||
mainLayout->addLayout(hl);
|
||||
mainLayout->addStretch();
|
||||
|
||||
setObjectName(QStringLiteral("contextContainer"));
|
||||
setLayout(mainLayout);
|
||||
|
||||
for (auto t : canvasDock->transitions) {
|
||||
auto name = QString::fromUtf8(obs_source_get_name(t));
|
||||
transition->addItem(name);
|
||||
if (obs_weak_source_references_source(canvasDock->source, t)) {
|
||||
transition->setCurrentText(name);
|
||||
bool config = obs_is_source_configurable(obs_source_get_unversioned_id(t));
|
||||
removeButton->setEnabled(config);
|
||||
propsButton->setEnabled(config);
|
||||
}
|
||||
}
|
||||
connect(transition, &QComboBox::currentTextChanged, [this] {
|
||||
auto tn = transition->currentText().toUtf8();
|
||||
auto t = canvasDock->GetTransition(tn.constData());
|
||||
if (!t)
|
||||
return;
|
||||
canvasDock->SwapTransition(t);
|
||||
bool config = obs_is_source_configurable(obs_source_get_unversioned_id(t));
|
||||
removeButton->setEnabled(config);
|
||||
propsButton->setEnabled(config);
|
||||
});
|
||||
}
|
||||
|
||||
CanvasTransitionsDock::~CanvasTransitionsDock() {}
|
||||
25
transitions-dock.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QComboBox>
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
|
||||
class CanvasDock;
|
||||
|
||||
class CanvasTransitionsDock : public QFrame {
|
||||
Q_OBJECT
|
||||
friend class CanvasDock;
|
||||
|
||||
private:
|
||||
CanvasDock *canvasDock;
|
||||
QComboBox *transition;
|
||||
QSpinBox *duration;
|
||||
QPushButton *removeButton;
|
||||
QPushButton *propsButton;
|
||||
|
||||
public:
|
||||
CanvasTransitionsDock(CanvasDock *canvas_dock, QWidget *parent = nullptr);
|
||||
~CanvasTransitionsDock();
|
||||
};
|
||||