Compare commits

..

No commits in common. "master" and "release-0.6.0" have entirely different histories.

39 changed files with 281 additions and 1909 deletions

4
.gitignore vendored
View File

@ -1,14 +1,10 @@
.cache
.clangd .clangd
.conanbuild .conanbuild
.current_build .current_build
.idea
__pycache__
CMakeLists.txt.user CMakeLists.txt.user
Session.vim Session.vim
build build
compile_commands.json compile_commands.json
cmake-build-debug
dist dist
graph_info.json graph_info.json
tags tags

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

17
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompDBSettings">
<option name="linkedExternalProjectsSettings">
<CompDBProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</CompDBProjectSettings>
</option>
</component>
<component name="CompDBWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="ExternalStorageConfigurationManager" enabled="true" />
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -2,7 +2,7 @@
source: source:
- . - .
copyright_notice: |- copyright_notice: |-
Copyright 2021 - 2024 gary@drinkingtea.net Copyright 2021 gary@drinkingtea.net
This Source Code Form is subject to the terms of the Mozilla Public This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -14,10 +14,6 @@ if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion")
endif() endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../") set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../")
if(QTDIR) if(QTDIR)
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib") set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib")

373
LICENSE
View File

@ -1,373 +0,0 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

View File

@ -3,15 +3,15 @@ BUILDCORE_PATH=deps/buildcore
VCPKG_PKGS= VCPKG_PKGS=
include ${BUILDCORE_PATH}/base.mk include ${BUILDCORE_PATH}/base.mk
ifeq ($(BC_VAR_OS),darwin) ifeq ($(OS),darwin)
PROJECT_EXECUTABLE=./build/${BC_VAR_CURRENT_BUILD}/bin/${PROJECT_NAME}.app/Contents/MacOS/SlideController PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/${PROJECT_NAME}.app/Contents/MacOS/SlideController
else else
PROJECT_EXECUTABLE=./build/${BC_VAR_CURRENT_BUILD}/bin/${PROJECT_NAME} PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/bin/SlideController
endif endif
.PHONY: run .PHONY: run
run: build run: install
${ENV_RUN} ${PROJECT_EXECUTABLE} ${ENV_RUN} ${PROJECT_EXECUTABLE}
.PHONY: debug .PHONY: debug
debug: build debug: install
${DEBUGGER} ${PROJECT_EXECUTABLE} ${ENV_RUN} gdb --args ${PROJECT_EXECUTABLE}

View File

@ -1,19 +0,0 @@
# Slide Controller 9000
## Build Prerequisites
* Install GCC, Clang, or Visual Studio with C++20 support
* Install Python 3
* Install Ninja, Make, and CMake
* [Qt6](https://www.qt.io/download-qt-installer-oss) (Network and Widgets)
* Consider also installing ccache for faster subsequent build times
## Build
Build options: release, debug, asan
make purge configure-{release,debug,asan} install
## Run
make run

View File

@ -11,7 +11,7 @@ set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/dist/${BUILDCORE_BUILD_CONFIG}")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
# enable ccache # enable ccache
@ -26,14 +26,9 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG) add_definitions(-DDEBUG)
else() else()
add_definitions(-DNDEBUG) add_definitions(-DNDEBUG)
if(APPLE)
set(CMAKE_OSX_ARCHITECTURES arm64;x86_64)
endif()
endif() endif()
if(MSVC) if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:preprocessor")
else()
# forces colored output when using ninja # forces colored output when using ninja
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
# enable warnings # enable warnings
@ -44,13 +39,12 @@ else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-field-initializers") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-field-initializers")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-null-dereference") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnull-dereference")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused-variable") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused-variable")
# release build options # release build options

187
deps/buildcore/base.mk vendored
View File

@ -1,5 +1,5 @@
# #
# Copyright 2016 - 2023 gary@drinkingtea.net # Copyright 2016 - 2021 gary@drinkingtea.net
# #
# This Source Code Form is subject to the terms of the Mozilla Public # This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this # License, v. 2.0. If a copy of the MPL was not distributed with this
@ -9,103 +9,78 @@
ifeq (${OS},Windows_NT) ifeq (${OS},Windows_NT)
SHELL := powershell.exe SHELL := powershell.exe
.SHELLFLAGS := -NoProfile -Command .SHELLFLAGS := -NoProfile -Command
BC_VAR_OS=windows OS=windows
BC_CMD_HOST_PY3=python HOST_ENV=${OS}
else else
BC_VAR_OS=$(shell uname | tr [:upper:] [:lower:]) OS=$(shell uname | tr [:upper:] [:lower:])
ifneq ($(shell which python3 2> /dev/null),) HOST_ENV=${OS}-$(shell uname -m)
BC_CMD_HOST_PY3=python3 endif
else
ifeq ($(shell python -c 'import sys; print(sys.version_info[0])'),3) DEVENV=devenv$(shell pwd | sed 's/\//-/g')
BC_CMD_HOST_PY3=python DEVENV_IMAGE=${PROJECT_NAME}-devenv
else ifneq ($(shell which docker 2> /dev/null),)
echo 'Please install Python3 on host' ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
exit 1 ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
endif
endif endif
endif endif
ifeq ($(shell ${ENV_RUN} python -c 'import sys; print(sys.version_info[0])'),3)
ifdef BC_VAR_USE_DOCKER_DEVENV PYTHON3=python
ifneq ($(shell which docker 2> /dev/null),)
BC_VAR_DEVENV=devenv$(shell pwd | sed 's/\//-/g')
BC_VAR_DEVENV_IMAGE=${BC_VAR_PROJECT_NAME}-devenv
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${BC_VAR_DEVENV} 2>&1),running)
BC_CMD_ENVRUN=docker exec -i -t --user $(shell id -u ${USER}) ${BC_VAR_DEVENV}
endif
endif
ifneq ($(shell ${BC_CMD_ENVRUN} which python3 2> /dev/null),)
BC_CMD_PY3=${BC_CMD_ENVRUN} python3
else
ifeq ($(shell ${BC_CMD_ENVRUN} python -c 'import sys; print(sys.version_info[0])'),3)
BC_CMD_PY3=${BC_CMD_ENVRUN} python
else
echo 'Please install Python3 in devenv'
exit 1
endif
endif
ifndef BC_VAR_DEVENV_ROOT
BC_VAR_DEVENV_ROOT="."
endif
else else
BC_CMD_PY3=${BC_CMD_HOST_PY3} PYTHON3=python3
endif endif
BC_VAR_SCRIPTS=${BUILDCORE_PATH}/scripts SCRIPTS=${BUILDCORE_PATH}/scripts
BC_CMD_SETUP_BUILD=${BC_CMD_PY3} ${BC_VAR_SCRIPTS}/setup-build.py SETUP_BUILD=${PYTHON3} ${SCRIPTS}/setup-build.py
BC_CMD_PYBB=${BC_CMD_PY3} ${BC_VAR_SCRIPTS}/pybb.py PYBB=${PYTHON3} ${SCRIPTS}/pybb.py
BC_CMD_HOST_PYBB=${BC_CMD_HOST_PY3} ${BC_VAR_SCRIPTS}/pybb.py CMAKE_BUILD=${PYBB} cmake-build
BC_CMD_CMAKE_BUILD=${BC_CMD_PYBB} cmake-build CTEST=${PYBB} ctest-all
BC_CMD_GETENV=${BC_CMD_PYBB} getenv RM_RF=${PYBB} rm
BC_CMD_CTEST=${BC_CMD_PYBB} ctest-all ifdef USE_VCPKG
BC_CMD_RM_RF=${BC_CMD_PYBB} rm ifndef VCPKG_DIR_BASE
BC_CMD_MKDIR_P=${BC_CMD_PYBB} mkdir VCPKG_DIR_BASE=.vcpkg
BC_CMD_CAT=${BC_CMD_PYBB} cat endif
BC_CMD_DEBUGGER=${BC_CMD_PYBB} debug ifndef VCPKG_VERSION
BC_CMD_HOST_DEBUGGER=${BC_CMD_HOST_PYBB} debug VCPKG_VERSION=2020.06
BC_VAR_HOSTENV=$(shell ${BC_CMD_ENVRUN} ${BC_CMD_PYBB} hostenv) endif
BC_VAR_BUILD_PATH=build VCPKG_TOOLCHAIN=--toolchain=${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
BC_VAR_CURRENT_BUILD=$(BC_VAR_HOSTENV)-$(shell ${BC_CMD_ENVRUN} ${BC_CMD_CAT} .current_build) endif
ifeq ($(OS),darwin)
DEBUGGER=lldb
else
DEBUGGER=gdb --args
endif
ifdef BC_VAR_USE_VCPKG VCPKG_DIR=$(VCPKG_DIR_BASE)/$(VCPKG_VERSION)-$(HOST_ENV)
ifndef BC_VAR_VCPKG_DIR_BASE CURRENT_BUILD=$(HOST_ENV)-$(shell ${PYBB} cat .current_build)
BC_VAR_VCPKG_DIR_BASE=.vcpkg
endif
ifndef BC_VAR_VCPKG_VERSION
BC_VAR_VCPKG_VERSION=2023.08.09
endif
endif
.PHONY: build .PHONY: build
build: build:
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} ${ENV_RUN} ${CMAKE_BUILD} build
.PHONY: install .PHONY: install
install: install:
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} install ${ENV_RUN} ${CMAKE_BUILD} build install
.PHONY: clean .PHONY: clean
clean: clean:
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} clean ${ENV_RUN} ${CMAKE_BUILD} build clean
.PHONY: purge .PHONY: purge
purge: purge:
${BC_CMD_RM_RF} .current_build ${ENV_RUN} ${RM_RF} .current_build
${BC_CMD_RM_RF} ${BC_VAR_BUILD_PATH} ${ENV_RUN} ${RM_RF} build
${BC_CMD_RM_RF} dist ${ENV_RUN} ${RM_RF} dist
${BC_CMD_RM_RF} compile_commands.json
.PHONY: test .PHONY: test
test: build test: build
${BC_CMD_ENVRUN} mypy ${BC_VAR_SCRIPTS} ${ENV_RUN} ${CMAKE_BUILD} build test
${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} test
.PHONY: test-verbose .PHONY: test-verbose
test-verbose: build test-verbose: build
${BC_CMD_CTEST} ${BC_VAR_BUILD_PATH} --output-on-failure ${ENV_RUN} ${CTEST} build --output-on-failure
.PHONY: test-rerun-verbose .PHONY: test-rerun-verbose
test-rerun-verbose: build test-rerun-verbose: build
${BC_CMD_CTEST} ${BC_VAR_BUILD_PATH} --rerun-failed --output-on-failure ${ENV_RUN} ${CTEST} build --rerun-failed --output-on-failure
ifdef BC_VAR_USE_DOCKER_DEVENV
.PHONY: devenv-image .PHONY: devenv-image
devenv-image: devenv-image:
docker build ${BC_VAR_DEVENV_ROOT} -t ${BC_VAR_DEVENV_IMAGE} docker build . -t ${DEVENV_IMAGE}
.PHONY: devenv-create .PHONY: devenv-create
devenv-create: devenv-create:
docker run -d \ docker run -d \
@ -117,77 +92,67 @@ devenv-create:
-v $(shell pwd):/usr/src/project \ -v $(shell pwd):/usr/src/project \
-v /dev/shm:/dev/shm \ -v /dev/shm:/dev/shm \
--restart=always \ --restart=always \
--name ${BC_VAR_DEVENV} \ --name ${DEVENV} \
-t ${BC_VAR_DEVENV_IMAGE} bash -t ${DEVENV_IMAGE} bash
.PHONY: devenv-destroy .PHONY: devenv-destroy
devenv-destroy: devenv-destroy:
docker rm -f ${BC_VAR_DEVENV} docker rm -f ${DEVENV}
ifdef BC_CMD_ENVRUN ifdef ENV_RUN
.PHONY: devenv-shell .PHONY: devenv-shell
devenv-shell: devenv-shell:
${BC_CMD_ENVRUN} bash ${ENV_RUN} bash
endif
endif endif
ifdef BC_VAR_USE_VCPKG ifdef USE_VCPKG
BC_VAR_VCPKG_TOOLCHAIN=--toolchain=${BC_VAR_VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
BC_VAR_VCPKG_DIR=$(BC_VAR_VCPKG_DIR_BASE)/$(BC_VAR_VCPKG_VERSION)-$(BC_VAR_HOSTENV)
.PHONY: vcpkg .PHONY: vcpkg
vcpkg: ${BC_VAR_VCPKG_DIR} vcpkg-install vcpkg: ${VCPKG_DIR} vcpkg-install
${BC_VAR_VCPKG_DIR}: ${VCPKG_DIR}:
${BC_CMD_RM_RF} ${BC_VAR_VCPKG_DIR} ${ENV_RUN} ${RM_RF} ${VCPKG_DIR}
${BC_CMD_PYBB} mkdir ${BC_VAR_VCPKG_DIR_BASE} ${ENV_RUN} mkdir -p ${VCPKG_DIR_BASE}
${BC_CMD_ENVRUN} git clone -b release --depth 1 --branch ${BC_VAR_VCPKG_VERSION} https://github.com/microsoft/vcpkg.git ${BC_VAR_VCPKG_DIR} ${ENV_RUN} git clone -b release --depth 1 --branch ${VCPKG_VERSION} https://github.com/microsoft/vcpkg.git ${VCPKG_DIR}
ifneq (${BC_VAR_OS},windows) ifneq (${OS},windows)
${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/bootstrap-vcpkg.sh ${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.sh
else else
${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/bootstrap-vcpkg.bat ${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.bat
endif endif
.PHONY: vcpkg-install .PHONY: vcpkg-install
vcpkg-install: vcpkg-install:
ifneq (${BC_VAR_OS},windows) ifneq (${OS},windows)
${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/vcpkg install ${BC_VAR_VCPKG_PKGS} ${VCPKG_DIR}/vcpkg install ${VCPKG_PKGS}
else else
${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/vcpkg install --triplet x64-windows ${BC_VAR_VCPKG_PKGS} ${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS}
endif endif
else ifdef USE_CONAN # USE_VCPKG / USE_CONAN #################################### else # USE_VCPKG ################################################
.PHONY: setup-conan .PHONY: setup-conan
conan-config: conan-config:
${BC_CMD_ENVRUN} conan profile new ${BC_VAR_PROJECT_NAME} --detect --force ${ENV_RUN} conan profile new ${PROJECT_NAME} --detect --force
ifeq ($(BC_VAR_OS),linux) ifeq ($(OS),linux)
${BC_CMD_ENVRUN} conan profile update settings.compiler.libcxx=libstdc++11 ${BC_VAR_PROJECT_NAME} ${ENV_RUN} conan profile update settings.compiler.libcxx=libstdc++11 ${PROJECT_NAME}
else
${BC_CMD_ENVRUN} conan profile update settings.compiler.cppstd=20 ${BC_VAR_PROJECT_NAME}
ifeq ($(BC_VAR_OS),windows)
${BC_CMD_ENVRUN} conan profile update settings.compiler.runtime=static ${BC_VAR_PROJECT_NAME}
endif endif
endif
.PHONY: conan .PHONY: conan
conan: conan:
${BC_CMD_PYBB} conan-install ${BC_VAR_PROJECT_NAME} ${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME}
endif # USE_CONAN ############################################### #@mkdir -p .conanbuild && cd .conanbuild && conan install ../ --build=missing -pr=${PROJECT_NAME}
endif # USE_VCPKG ###############################################
ifeq (${BC_VAR_OS},darwin)
.PHONY: configure-xcode .PHONY: configure-xcode
configure-xcode: configure-xcode:
${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 --build_root=${BC_VAR_BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0
endif
.PHONY: configure-release .PHONY: configure-release
configure-release: configure-release:
${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_type=release --build_root=${BC_VAR_BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=release
.PHONY: configure-debug .PHONY: configure-debug
configure-debug: configure-debug:
${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BC_VAR_BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug
.PHONY: configure-asan .PHONY: configure-asan
configure-asan: configure-asan:
${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BC_VAR_BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan

View File

@ -1,28 +0,0 @@
#! /usr/bin/env python3
#
# Copyright 2016 - 2022 gary@drinkingtea.net
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
import argparse
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--out-cpp', help='path to output cpp file')
parser.add_argument('--out-hpp', help='path to output hpp file')
args = parser.parse_args()
return 0
if __name__ == '__main__':
try:
err = main()
sys.exit(err)
except KeyboardInterrupt:
sys.exit(1)

View File

@ -8,156 +8,68 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# #
# "Python Busy Box" - adds cross-platform equivalents to Unix commands that # "Python Busy Box" - adds cross platform equivalents to Unix commands that
# don't translate well to that other operating system # don't translate well to that other operating system
import os import os
import platform
import shutil import shutil
import subprocess import subprocess
import sys import sys
from typing import List, Optional
import util
def mkdir(path: str) -> int: def cat(path):
try: try:
util.mkdir_p(path) with open(path) as f:
except Exception: data = f.read()
print(data)
return 0
except FileNotFoundError:
sys.stderr.write('cat: {}: no such file or directory\n'.format(path))
return 1 return 1
return 0
def rm_multi(paths: List[str]): def mkdir(path):
for path in paths: if not os.path.exists(path) and os.path.isdir(path):
util.rm(path) os.mkdir(path)
def ctest_all() -> int: # this exists because Windows is utterly incapable of providing a proper rm -rf
base_path = sys.argv[2] def rm(path):
if not os.path.isdir(base_path): if (os.path.exists(path) or os.path.islink(path)) and not os.path.isdir(path):
# no generated projects os.remove(path)
return 0 elif os.path.isdir(path):
args = ['ctest'] + sys.argv[3:] shutil.rmtree(path)
orig_dir = os.getcwd()
for d in os.listdir(base_path):
os.chdir(os.path.join(orig_dir, base_path, d))
err = subprocess.run(args).returncode
if err != 0:
return err
return 0
def cmake_build(base_path: str, target: Optional[str]) -> int: def cmake_build(base_path, target):
if not os.path.isdir(base_path): if not os.path.isdir(base_path):
# nothing to build # nothing to build
return 0 return 0
for d in os.listdir(base_path): for d in os.listdir(base_path):
path = os.path.join(base_path, d) args = ['cmake', '--build', os.path.join(base_path, d)]
if not os.path.isdir(path):
continue
args = ['cmake', '--build', path]
if target is not None: if target is not None:
args.extend(['--target', target]) args.extend(['--target', target])
err = subprocess.run(args).returncode err = subprocess.run(args).returncode
if err != 0: if err != 0:
return err return err
return 0
def conan() -> int: def main():
project_name = sys.argv[2]
conan_dir = '.conanbuild'
err = 0
try:
mkdir(conan_dir)
except Exception:
return 1
if err != 0:
return err
args = ['conan', 'install', '../', '--build=missing', '-pr', project_name]
os.chdir(conan_dir)
return subprocess.run(args).returncode
def cat(paths: List[str]) -> int:
for path in paths:
try:
with open(path) as f:
data = f.read()
print(data)
except FileNotFoundError:
sys.stderr.write(f'cat: {path}: no such file or directory\n')
return 1
return 0
def debug(paths: List[str]) -> int:
if shutil.which('gdb') is not None:
args = ['gdb', '--args']
elif shutil.which('lldb') is not None:
args = ['lldb', '--']
else:
sys.stderr.write('debug: could not find a supported debugger\n')
return 1
args.extend(paths)
return subprocess.run(args).returncode
def get_env(var_name: str) -> int:
if var_name not in os.environ:
return 1
print(os.environ[var_name])
return 0
def hostname() -> int:
print(platform.node())
return 0
def host_env() -> int:
os_name = platform.system().lower()
arch = util.get_arch()
print(f'{os_name}-{arch}')
return 0
def clarg(idx: int) -> Optional[str]:
return sys.argv[idx] if len(sys.argv) > idx else None
def main() -> int:
err = 0
if sys.argv[1] == 'mkdir': if sys.argv[1] == 'mkdir':
err = mkdir(sys.argv[2]) mkdir(sys.argv[2])
elif sys.argv[1] == 'rm': elif sys.argv[1] == 'rm':
rm_multi(sys.argv[2:]) for i in range(2, len(sys.argv)):
elif sys.argv[1] == 'conan-install': rm(sys.argv[i])
err = conan()
elif sys.argv[1] == 'ctest-all':
err = ctest_all()
elif sys.argv[1] == 'cmake-build': elif sys.argv[1] == 'cmake-build':
err = cmake_build(sys.argv[2], clarg(3)) err = cmake_build(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None)
sys.exit(err)
elif sys.argv[1] == 'cat': elif sys.argv[1] == 'cat':
err = cat(sys.argv[2:]) err = cat(sys.argv[2])
elif sys.argv[1] == 'debug': sys.exit(err)
err = debug(sys.argv[2:])
elif sys.argv[1] == 'getenv':
err = get_env(sys.argv[2])
elif sys.argv[1] == 'hostname':
err = hostname()
elif sys.argv[1] == 'hostenv':
err = host_env()
else:
sys.stderr.write('Command not found\n')
err = 1
return err
if __name__ == '__main__': if __name__ == '__main__':
try: try:
sys.exit(main()) main()
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(1) sys.exit(1)

View File

@ -15,35 +15,17 @@ import shutil
import subprocess import subprocess
import sys import sys
import util from pybb import mkdir, rm
def main() -> int: def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument('--target', help='Platform target',
'--target', default='{:s}-{:s}'.format(sys.platform, platform.machine()))
help='Platform target', parser.add_argument('--build_type', help='Build type (asan,debug,release)', default='release')
default=f'{util.get_os()}-{util.get_arch()}') parser.add_argument('--build_tool', help='Build tool (default,xcode)', default='')
parser.add_argument( parser.add_argument('--toolchain', help='Path to CMake toolchain file', default='')
'--build_type', parser.add_argument('--current_build', help='Indicates whether or not to make this the active build', default=1)
help='Build type (asan,debug,release)',
default='release')
parser.add_argument(
'--build_tool',
help='Build tool (default,xcode)',
default='')
parser.add_argument(
'--build_root',
help='Path to the root build directory (must be in project dir)',
default='build')
parser.add_argument(
'--toolchain',
help='Path to CMake toolchain file',
default='')
parser.add_argument(
'--current_build',
help='Indicates whether or not to make this the active build',
default=1)
args = parser.parse_args() args = parser.parse_args()
if args.build_type == 'asan': if args.build_type == 'asan':
@ -57,7 +39,7 @@ def main() -> int:
sanitizer_status = 'OFF' sanitizer_status = 'OFF'
else: else:
print('Error: Invalid build tool') print('Error: Invalid build tool')
return 1 sys.exit(1)
if args.build_tool == 'xcode': if args.build_tool == 'xcode':
build_config = '{:s}-{:s}'.format(args.target, args.build_tool) build_config = '{:s}-{:s}'.format(args.target, args.build_tool)
@ -78,46 +60,35 @@ def main() -> int:
build_tool = '-GXcode' build_tool = '-GXcode'
else: else:
print('Error: Invalid build tool') print('Error: Invalid build tool')
return 1 sys.exit(1)
project_dir = os.getcwd() project_dir = os.getcwd()
build_dir = f'{project_dir}/{args.build_root}/{build_config}' build_dir = '{:s}/build/{:s}'.format(project_dir, build_config)
util.rm(build_dir) rm(build_dir)
cmake_cmd = [ mkdir(build_dir)
'cmake', '-S', project_dir, '-B', build_dir, subprocess.run(['cmake', '-S', project_dir, '-B', build_dir, build_tool,
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
'-DCMAKE_TOOLCHAIN_FILE={:s}'.format(args.toolchain), '-DCMAKE_TOOLCHAIN_FILE={:s}'.format(args.toolchain),
'-DCMAKE_BUILD_TYPE={:s}'.format(build_type_arg), '-DCMAKE_BUILD_TYPE={:s}'.format(build_type_arg),
'-DUSE_ASAN={:s}'.format(sanitizer_status), '-DUSE_ASAN={:s}'.format(sanitizer_status),
'-DBUILDCORE_BUILD_CONFIG={:s}'.format(build_config), '-DBUILDCORE_BUILD_CONFIG={:s}'.format(build_config),
'-DBUILDCORE_TARGET={:s}'.format(args.target), '-DBUILDCORE_TARGET={:s}'.format(args.target),
] qt_path,
if build_tool != '': ])
cmake_cmd.append(build_tool)
if qt_path != '':
cmake_cmd.append(qt_path)
if platform.system() == 'Windows' and platform.system() == 'AMD64':
cmake_cmd.append('-A x64')
cmake_err = subprocess.run(cmake_cmd).returncode mkdir('dist')
if cmake_err != 0:
return cmake_err
util.mkdir_p('dist')
if int(args.current_build) != 0: if int(args.current_build) != 0:
cb = open('.current_build', 'w') cb = open('.current_build', 'w')
cb.write(args.build_type) cb.write(args.build_type)
cb.close() cb.close()
util.rm('compile_commands.json') rm('compile_commands.json')
if platform.system() != 'Windows': if platform.system() != 'Windows':
os.symlink(f'{build_dir}/compile_commands.json', os.symlink('build/{:s}/compile_commands.json'.format(build_config), 'compile_commands.json')
'compile_commands.json')
return 0
if __name__ == '__main__': if __name__ == '__main__':
try: try:
sys.exit(main()) main()
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(1) sys.exit(1)

View File

@ -1,38 +0,0 @@
#
# Copyright 2016 - 2021 gary@drinkingtea.net
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
import os
import platform
import shutil
def mkdir_p(path: str):
if not os.path.exists(path):
os.mkdir(path)
# this exists because Windows is utterly incapable of providing a proper rm -rf
def rm(path: str):
file_exists = os.path.exists(path)
is_link = os.path.islink(path)
is_dir = os.path.isdir(path)
if (file_exists or is_link) and not is_dir:
os.remove(path)
elif os.path.isdir(path):
shutil.rmtree(path)
def get_os() -> str:
return platform.system().lower()
def get_arch() -> str:
arch = platform.machine().lower()
if arch == 'amd64':
arch = 'x86_64'
return arch

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

View File

@ -33,7 +33,7 @@ class RqstHandler(BaseHTTPRequestHandler):
def run(name): def run(name):
httpd = HTTPServer(('0.0.0.0', 9302), RqstHandler) httpd = HTTPServer(('127.0.0.1', 9302), RqstHandler)
httpd.serve_forever() httpd.serve_forever()

View File

@ -3,20 +3,16 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
find_package(QT NAMES Qt6 COMPONENTS Network Widgets REQUIRED) find_package(QT NAMES Qt6 Qt5 COMPONENTS Network Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED)
add_executable( add_executable(
SlideController MACOSX_BUNDLE WIN32 SlideController MACOSX_BUNDLE WIN32
cameraclient.cpp
main.cpp main.cpp
mainwindow.cpp mainwindow.cpp
obsclient.cpp obsclient.cpp
openlpclient.cpp openlpclient.cpp
settingsdata.cpp
settingsdialog.cpp
slideview.cpp slideview.cpp
sc9k.rc
) )
target_link_libraries( target_link_libraries(

View File

@ -1,113 +0,0 @@
/*
* Copyright 2021 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <QNetworkReply>
#include <QSettings>
#include "consts.hpp"
#include "settingsdata.hpp"
#include "cameraclient.hpp"
CameraClient::CameraClient(QObject *parent): QObject(parent) {
setBaseUrl();
m_pollTimer.start(1000);
connect(&m_pollTimer, &QTimer::timeout, this, &CameraClient::poll);
connect(m_pollingNam, &QNetworkAccessManager::finished, this, &CameraClient::handlePollResponse);
}
void CameraClient::setPresetVC(int preset, VideoConfig const&vc) {
if (preset > 0 && preset < MaxCameraPresets) {
get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset));
setBrightness(vc.brightness);
setSaturation(vc.saturation);
setContrast(vc.contrast);
setSharpness(vc.sharpness);
setHue(vc.hue);
}
}
void CameraClient::setPreset(int preset) {
if (preset > 0 && preset < MaxCameraPresets) {
get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset));
auto const vc = getVideoConfig()[preset - 1];
setBrightness(vc.brightness);
setSaturation(vc.saturation);
setContrast(vc.contrast);
setSharpness(vc.sharpness);
setHue(vc.hue);
}
}
void CameraClient::reboot() {
post("/cgi-bin/param.cgi?post_reboot");
emit pollFailed();
}
void CameraClient::setBaseUrl() {
auto const [host, port] = getCameraConnectionData();
m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
}
void CameraClient::setBrightness(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&bright&%1").arg(val));
}
}
void CameraClient::setSaturation(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&saturation&%1").arg(val));
}
}
void CameraClient::setContrast(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&contrast&%1").arg(val));
}
}
void CameraClient::setSharpness(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&sharpness&%1").arg(val));
}
}
void CameraClient::setHue(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&hue&%1").arg(val));
}
}
void CameraClient::get(QString const&urlExt) {
QUrl const url{QString{m_baseUrl} + urlExt};
QNetworkRequest rqst{url};
auto const reply = m_nam->get(rqst);
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
}
void CameraClient::post(QString const&urlExt) {
QNetworkRequest const rqst{QUrl{QString{m_baseUrl} + urlExt}};
auto const reply = m_nam->post(rqst, QByteArray{});
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
}
void CameraClient::poll() {
QUrl const url{QString{m_baseUrl} + "/cgi-bin/param.cgi?get_device_conf"};
QNetworkRequest const rqst{url};
m_pollingNam->get(rqst);
}
void CameraClient::handlePollResponse(QNetworkReply *reply) {
reply->deleteLater();
if (reply->error()) {
qDebug() << "CameraClient error response:" << reply->errorString();
emit pollFailed();
return;
}
emit pollUpdate();
}

View File

@ -1,60 +0,0 @@
/*
* Copyright 2021 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <QNetworkAccessManager>
#include <QObject>
#include <QTimer>
class CameraClient: public QObject {
Q_OBJECT
private:
QString m_baseUrl;
QNetworkAccessManager *const m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *const m_pollingNam = new QNetworkAccessManager(this);
QTimer m_pollTimer;
public:
explicit CameraClient(QObject *parent = nullptr);
void setPresetVC(int preset, struct VideoConfig const&vc);
void setPreset(int preset);
void reboot();
public slots:
void setBaseUrl();
private:
void setBrightness(int val);
void setSaturation(int val);
void setContrast(int val);
void setSharpness(int val);
void setHue(int val);
void get(QString const&url);
void post(QString const&url);
void poll();
void handlePollResponse(QNetworkReply *reply);
signals:
void pollUpdate();
void pollFailed();
};

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,7 +8,4 @@
#pragma once #pragma once
constexpr auto MaxCameraPresets = 9; constexpr auto SlideHost = "127.0.0.1";
constexpr auto MaxViews = 9;
constexpr auto Version = "1.0.0";

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,18 +7,12 @@
*/ */
#include <QApplication> #include <QApplication>
#include <QSettings>
#include "mainwindow.hpp" #include "mainwindow.hpp"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
QSettings::setDefaultFormat(QSettings::Format::IniFormat);
#ifndef __APPLE__
QApplication::setStyle("Fusion");
#endif
QApplication a(argc, argv); QApplication a(argc, argv);
QApplication::setOrganizationName("DrinkingTea"); QApplication::setApplicationName(QObject::tr("Slide Controller 9000"));
QApplication::setApplicationName("Slide Controller 9000");
MainWindow w; MainWindow w;
w.show(); w.show();
return QApplication::exec(); return QApplication::exec();

View File

@ -1,225 +1,78 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include <QApplication>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMenuBar>
#include <QMessageBox>
#include <QPushButton> #include <QPushButton>
#include <QStatusBar> #include <QStatusBar>
#include "settingsdialog.hpp"
#include "slideview.hpp" #include "slideview.hpp"
#include "mainwindow.hpp" #include "mainwindow.hpp"
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) { MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
move(0, 0); move(0, 0);
setFixedSize(610, 555); setFixedSize(600, 555);
setWindowTitle(tr("Slide Controller 9000")); setWindowTitle(tr("Slide Controller 9000"));
setupMenu(); const auto mainWidget = new QWidget(this);
auto const mainWidget = new QWidget(this); const auto rootLyt = new QVBoxLayout;
m_rootLyt = new QVBoxLayout; const auto controlsLayout = new QGridLayout;
auto const controlsLayout = new QGridLayout;
m_slideView = new SlideView(this); m_slideView = new SlideView(this);
setCentralWidget(mainWidget); setCentralWidget(mainWidget);
mainWidget->setLayout(m_rootLyt); mainWidget->setLayout(rootLyt);
m_rootLyt->addWidget(m_slideView); rootLyt->addWidget(m_slideView);
m_rootLyt->addLayout(controlsLayout); rootLyt->addLayout(controlsLayout);
// setup slide controls // setup slide controls
auto const btnPrevSong = new QPushButton(tr("Previous Song"), this); const auto showHideLyt = new QHBoxLayout;
auto const btnPrevSlide = new QPushButton(tr("Previous Slide"), this); rootLyt->addLayout(showHideLyt);
auto const btnNextSlide = new QPushButton(tr("Next Slide"), this); const auto btnPrevSong = new QPushButton(tr("Previous Song (Left)"), this);
auto const btnNextSong = new QPushButton(tr("Next Song"), this); const auto btnPrevSlide = new QPushButton(tr("Previous Slide (Up)"), this);
btnPrevSong->setToolTip(tr("Change to previous song (left arrow key)")); const auto btnNextSlide = new QPushButton(tr("Next Slide (Down)"), this);
btnPrevSlide->setToolTip(tr("Change to previous slide (up arrow key)")); const auto btnNextSong = new QPushButton(tr("Next Song (Right)"), this);
btnNextSong->setToolTip(tr("Change to next song (right arrow key)")); const auto btnHideSlides = new QPushButton(tr("Hide (1)"), this);
btnNextSlide->setToolTip(tr("Change to next slide (down arrow key)")); const auto btnOpenLpShowSlides = new QPushButton(tr("Show in OpenLP Only (2)"), this);
const auto btnShowSlides = new QPushButton(tr("Show (3)"), mainWidget);
controlsLayout->addWidget(btnPrevSlide, 0, 1); controlsLayout->addWidget(btnPrevSlide, 0, 1);
controlsLayout->addWidget(btnNextSlide, 0, 2); controlsLayout->addWidget(btnNextSlide, 0, 2);
controlsLayout->addWidget(btnPrevSong, 0, 0); controlsLayout->addWidget(btnPrevSong, 0, 0);
controlsLayout->addWidget(btnNextSong, 0, 3); controlsLayout->addWidget(btnNextSong, 0, 3);
controlsLayout->setSpacing(2); showHideLyt->addWidget(btnHideSlides);
showHideLyt->addWidget(btnOpenLpShowSlides);
showHideLyt->addWidget(btnShowSlides);
btnNextSong->setShortcut(Qt::Key_Right); btnNextSong->setShortcut(Qt::Key_Right);
btnPrevSong->setShortcut(Qt::Key_Left); btnPrevSong->setShortcut(Qt::Key_Left);
btnNextSlide->setShortcut(Qt::Key_Down); btnNextSlide->setShortcut(Qt::Key_Down);
btnPrevSlide->setShortcut(Qt::Key_Up); btnPrevSlide->setShortcut(Qt::Key_Up);
btnNextSong->setFixedWidth(135); btnHideSlides->setShortcut(Qt::Key_1);
btnPrevSong->setFixedWidth(135); btnOpenLpShowSlides->setShortcut(Qt::Key_2);
btnNextSlide->setFixedWidth(135); btnHideSlides->setToolTip(tr("Also hides slides in OBS"));
btnPrevSlide->setFixedWidth(135); btnShowSlides->setShortcut(Qt::Key_3);
setupViewControls(m_rootLyt);
connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide); connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide);
connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide); connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide);
connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong); connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong);
connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong); connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong);
connect(btnHideSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen);
connect(btnHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
connect(btnShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, m_slideView, &SlideView::pollUpdate); connect(&m_openlpClient, &OpenLPClient::pollUpdate, m_slideView, &SlideView::pollUpdate);
connect(&m_openlpClient, &OpenLPClient::songListUpdate, m_slideView, &SlideView::songListUpdate); connect(&m_openlpClient, &OpenLPClient::songListUpdate, m_slideView, &SlideView::songListUpdate);
connect(&m_openlpClient, &OpenLPClient::slideListUpdate, m_slideView, &SlideView::slideListUpdate); connect(&m_openlpClient, &OpenLPClient::slideListUpdate, m_slideView, &SlideView::slideListUpdate);
connect(&m_openlpClient, &OpenLPClient::pollFailed, m_slideView, &SlideView::reset); connect(&m_openlpClient, &OpenLPClient::pollFailed, m_slideView, &SlideView::reset);
connect(m_slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong); connect(m_slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong);
connect(m_slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide); connect(m_slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide);
// setup scene selector
connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
// setup status bar // setup status bar
setStatusBar(new QStatusBar(this)); setStatusBar(new QStatusBar(this));
connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
connect(&m_openlpClient, &OpenLPClient::songChanged, this, &MainWindow::refreshStatusBar); connect(&m_openlpClient, &OpenLPClient::songChanged, this, &MainWindow::refreshStatusBar);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit); connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit); connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
refreshStatusBar(); refreshStatusBar();
connect(statusBar(), &QStatusBar::messageChanged, this, [this](QStringView const&msg) {
if (msg.empty()) {
refreshStatusBar();
}
});
}
void MainWindow::setupMenu() {
// file menu
{
auto const menu = menuBar()->addMenu(tr("&File"));
auto const settingsAct = new QAction(tr("&Settings"), this);
auto const quitAct = new QAction(tr("E&xit"), this);
settingsAct->setShortcuts(QKeySequence::Preferences);
connect(settingsAct, &QAction::triggered, this, &MainWindow::openSettings);
quitAct->setShortcuts(QKeySequence::Quit);
quitAct->setStatusTip(tr("Exit application"));
connect(quitAct, &QAction::triggered, &QApplication::quit);
menu->addAction(settingsAct);
menu->addAction(quitAct);
}
// slides menu
{
auto const menu = menuBar()->addMenu(tr("&Slides"));
auto const hideSlidesAct = new QAction(tr("&Hide Slides"), this);
hideSlidesAct->setShortcut(Qt::CTRL | Qt::Key_1);
connect(hideSlidesAct, &QAction::triggered, &m_openlpClient, &OpenLPClient::blankScreen);
connect(hideSlidesAct, &QAction::triggered, &m_obsClient, &OBSClient::hideSlides);
menu->addAction(hideSlidesAct);
auto const showSlidesInOpenLpAct = new QAction(tr("Show in &OpenLP Only"), this);
showSlidesInOpenLpAct->setShortcut(Qt::CTRL | Qt::Key_2);
connect(showSlidesInOpenLpAct, &QAction::triggered, &m_openlpClient, &OpenLPClient::showSlides);
connect(showSlidesInOpenLpAct, &QAction::triggered, &m_obsClient, &OBSClient::hideSlides);
menu->addAction(showSlidesInOpenLpAct);
auto const showSlidesAct = new QAction(tr("&Show Slides"), this);
showSlidesAct->setShortcut(Qt::CTRL | Qt::Key_3);
connect(showSlidesAct, &QAction::triggered, &m_obsClient, &OBSClient::showSlides);
connect(showSlidesAct, &QAction::triggered, &m_openlpClient, &OpenLPClient::showSlides);
menu->addAction(showSlidesAct);
}
// camera preset menu
{
auto const menu = menuBar()->addMenu(tr("&Camera"));
for (auto i = 0; i < std::min(9, MaxCameraPresets); ++i) {
auto const cameraPresetAct = new QAction(tr("Camera Preset &%1").arg(i + 1), this);
cameraPresetAct->setShortcut(Qt::ALT | static_cast<Qt::Key>(Qt::Key_1 + i));
connect(cameraPresetAct, &QAction::triggered, &m_cameraClient, [this, i] {
m_cameraClient.setPreset(i + 1);
});
menu->addAction(cameraPresetAct);
}
}
// help menu
{
auto const menu = menuBar()->addMenu(tr("&Help"));
auto const aboutAct = new QAction(tr("&About"), this);
connect(aboutAct, &QAction::triggered, &m_cameraClient, [this] {
QMessageBox about(this);
about.setText(tr(
R"(Slide Controller 9000 - %1
Build date: %2
Copyright 2021 - 2025 Gary Talent (gary@drinkingtea.net)
Slide Controller 9000 is released under the MPL 2.0
Built on Qt library under LGPL 2.0)").arg(Version, __DATE__));
about.exec();
});
menu->addAction(aboutAct);
}
}
void MainWindow::setupViewControlButtons(QVector<View> const&views, QGridLayout *viewCtlLyt) {
constexpr auto columns = 3;
auto const parent = viewCtlLyt->parentWidget();
for (auto i = 0; auto const&view : views) {
auto const x = i % columns;
auto const y = i / columns;
auto const name = QString("%1. %2").arg(i + 1).arg(view.name);
auto const btn = new QPushButton(name, parent);
btn->setShortcut(Qt::Key_1 + i);
viewCtlLyt->addWidget(btn, y, x);
auto const slides = view.slides;
auto const obsSlides = view.obsSlides;
auto const cameraPreset = view.cameraPreset;
connect(btn, &QPushButton::clicked, this, [this, slides, obsSlides, cameraPreset] {
m_cameraClient.setPreset(cameraPreset);
m_openlpClient.setSlidesVisible(slides);
m_obsClient.setSlidesVisible(obsSlides);
});
++i;
}
}
void MainWindow::setupViewControls(QVBoxLayout *rootLyt) {
auto views = getViews();
if (!m_viewControlsParent) {
m_viewControlsParent = new QWidget(rootLyt->parentWidget());
m_viewControlsParentLyt = new QHBoxLayout(m_viewControlsParent);
m_viewControlsParentLyt->setContentsMargins(0, 0, 0, 0);
rootLyt->addWidget(m_viewControlsParent);
}
delete m_viewControls;
m_viewControls = new QWidget(m_viewControlsParent);
m_viewControlsParentLyt->addWidget(m_viewControls);
auto const viewCtlLyt = new QGridLayout(m_viewControls);
viewCtlLyt->setSpacing(5);
if (views.empty()) {
views.emplace_back(View{
.name = tr("Hide"),
.slides = false,
.obsSlides = false,
});
views.emplace_back(View{
.name = tr("Show in OpenLP Only"),
.slides = true,
.obsSlides = false,
});
views.emplace_back(View{
.name = tr("Show"),
.slides = true,
.obsSlides = true,
});
}
setupViewControlButtons(views, viewCtlLyt);
}
void MainWindow::openSettings() {
SettingsDialog d(this);
connect(&d, &SettingsDialog::previewPreset, &m_cameraClient, &CameraClient::setPresetVC);
auto const result = d.exec();
if (result == QDialog::Accepted) {
m_cameraClient.setBaseUrl();
m_obsClient.setBaseUrl();
m_openlpClient.setBaseUrl();
setupViewControls(m_rootLyt);
}
}
void MainWindow::cameraConnectionInit() {
disconnect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
connect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost);
m_cameraConnected = true;
refreshStatusBar();
}
void MainWindow::cameraConnectionLost() {
disconnect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost);
connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
m_cameraConnected = false;
refreshStatusBar();
} }
void MainWindow::openLpConnectionInit() { void MainWindow::openLpConnectionInit() {
@ -251,10 +104,9 @@ void MainWindow::obsConnectionLost() {
} }
void MainWindow::refreshStatusBar() { void MainWindow::refreshStatusBar() {
auto const cameraStatus = m_cameraConnected ? tr("Camera: Connected") : tr("Camera: Not Connected"); const auto openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected");
auto const openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected"); const auto obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected");
auto const obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected"); const auto nextSong = m_openlpClient.getNextSong();
auto const nextSong = m_openlpClient.getNextSong(); const auto nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : "";
auto const nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : ""; statusBar()->showMessage(openLpStatus + " | " + obsStatus + nextSongTxt);
statusBar()->showMessage(cameraStatus + " | " + openLpStatus + " | " + obsStatus + nextSongTxt);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,46 +8,28 @@
#pragma once #pragma once
#include <cstdint>
#include <QMainWindow> #include <QMainWindow>
#include "cameraclient.hpp"
#include "obsclient.hpp" #include "obsclient.hpp"
#include "openlpclient.hpp" #include "openlpclient.hpp"
#include "settingsdata.hpp"
class MainWindow: public QMainWindow { class MainWindow: public QMainWindow {
Q_OBJECT Q_OBJECT
private: private:
CameraClient m_cameraClient;
OBSClient m_obsClient; OBSClient m_obsClient;
OpenLPClient m_openlpClient; OpenLPClient m_openlpClient;
class SlideView *m_slideView = nullptr; class SlideView *m_slideView = nullptr;
bool m_cameraConnected = false;
bool m_openLpConnected = false; bool m_openLpConnected = false;
bool m_obsConnected = false; bool m_obsConnected = false;
class QVBoxLayout *m_rootLyt = nullptr;
class QHBoxLayout *m_viewControlsParentLyt = nullptr;
class QWidget *m_viewControlsParent = nullptr;
class QWidget *m_viewControls = nullptr;
public: public:
explicit MainWindow(QWidget *parent = nullptr); explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override = default; ~MainWindow() override = default;
private: private slots:
void setupMenu();
void setupViewControlButtons(QVector<View> const&views, class QGridLayout *rootLyt);
void setupViewControls(class QVBoxLayout *rootLyt);
void openSettings();
void cameraConnectionInit();
void cameraConnectionLost();
void openLpConnectionInit(); void openLpConnectionInit();
void openLpConnectionLost(); void openLpConnectionLost();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,20 +8,17 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QSettings>
#include <QUrl> #include <QUrl>
#include "settingsdata.hpp"
#include "obsclient.hpp" #include "obsclient.hpp"
OBSClient::OBSClient(QObject *parent): QObject(parent) { OBSClient::OBSClient(QObject *parent): QObject(parent) {
setBaseUrl();
m_pollTimer.start(1000); m_pollTimer.start(1000);
connect(&m_pollTimer, &QTimer::timeout, this, &OBSClient::poll); connect(&m_pollTimer, &QTimer::timeout, this, &OBSClient::poll);
connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OBSClient::handlePollResponse); connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OBSClient::handlePollResponse);
} }
void OBSClient::setScene(QString const&scene) { void OBSClient::setScene(QString scene) {
get(QString("/Scene?name=%1").arg(scene)); get(QString("/Scene?name=%1").arg(scene));
} }
@ -33,7 +30,7 @@ void OBSClient::hideSlides() {
setScene("SpeakerScene"); setScene("SpeakerScene");
} }
void OBSClient::setSlidesVisible(bool state) { void OBSClient::setSlidesVisible(int state) {
if (state) { if (state) {
setScene("MusicScene"); setScene("MusicScene");
} else { } else {
@ -41,20 +38,15 @@ void OBSClient::setSlidesVisible(bool state) {
} }
} }
void OBSClient::setBaseUrl() { void OBSClient::get(QString urlExt) {
auto const [host, port] = getOBSConnectionData(); QUrl url(QString(BaseUrl) + urlExt);
m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
}
void OBSClient::get(QString const&urlExt) {
QUrl url(QString(m_baseUrl) + urlExt);
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
auto reply = m_nam->get(rqst); auto reply = m_nam->get(rqst);
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater); connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
} }
void OBSClient::poll() { void OBSClient::poll() {
QUrl url(QString(m_baseUrl) + "/ping"); QUrl url(QString(BaseUrl) + "/ping");
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
m_pollingNam->get(rqst); m_pollingNam->get(rqst);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -17,7 +17,7 @@
class OBSClient: public QObject { class OBSClient: public QObject {
Q_OBJECT Q_OBJECT
private: private:
QString m_baseUrl; const QString BaseUrl = QString("http://") + SlideHost + ":9302";
QNetworkAccessManager *m_nam = new QNetworkAccessManager(this); QNetworkAccessManager *m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this); QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this);
QTimer m_pollTimer; QTimer m_pollTimer;
@ -26,18 +26,16 @@ class OBSClient: public QObject {
explicit OBSClient(QObject *parent = nullptr); explicit OBSClient(QObject *parent = nullptr);
public slots: public slots:
void setScene(QString const&scene); void setScene(QString scene);
void showSlides(); void showSlides();
void hideSlides(); void hideSlides();
void setSlidesVisible(bool state); void setSlidesVisible(int state);
void setBaseUrl();
private: private:
void get(QString const&url); void get(QString url);
void poll(); void poll();

View File

@ -1,24 +1,20 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include <QHttpPart>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValueRef> #include <QJsonValueRef>
#include <QNetworkReply> #include <QNetworkReply>
#include <QSettings>
#include "settingsdata.hpp"
#include "openlpclient.hpp" #include "openlpclient.hpp"
OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) { OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) {
setBaseUrl();
poll(); poll();
m_pollTimer.start(250); m_pollTimer.start(250);
connect(&m_pollTimer, &QTimer::timeout, this, &OpenLPClient::poll); connect(&m_pollTimer, &QTimer::timeout, this, &OpenLPClient::poll);
@ -29,8 +25,8 @@ OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) {
} }
QString OpenLPClient::getNextSong() { QString OpenLPClient::getNextSong() {
auto const currentSong = m_songNameMap[m_currentSongId]; const auto currentSong = m_songNameMap[m_currentSongId];
auto const songIdx = m_songList.indexOf(currentSong) + 1; const auto songIdx = m_songList.indexOf(currentSong) + 1;
if (songIdx < m_songList.size()) { if (songIdx < m_songList.size()) {
return m_songList[songIdx]; return m_songList[songIdx];
} }
@ -38,19 +34,19 @@ QString OpenLPClient::getNextSong() {
} }
void OpenLPClient::nextSlide() { void OpenLPClient::nextSlide() {
post("/api/v2/controller/progress", R"({"action":"next"})"); get("/api/controller/live/next");
} }
void OpenLPClient::prevSlide() { void OpenLPClient::prevSlide() {
post("/api/v2/controller/progress", R"({"action":"previous"})"); get("/api/controller/live/previous");
} }
void OpenLPClient::nextSong() { void OpenLPClient::nextSong() {
post("/api/v2/service/progress", R"({"action":"next"})"); get("/api/service/next");
} }
void OpenLPClient::prevSong() { void OpenLPClient::prevSong() {
post("/api/v2/service/progress", R"({"action":"previous"})"); get("/api/service/previous");
} }
void OpenLPClient::blankScreen() { void OpenLPClient::blankScreen() {
@ -61,14 +57,6 @@ void OpenLPClient::showSlides() {
get("/api/display/show"); get("/api/display/show");
} }
void OpenLPClient::setSlidesVisible(bool value) {
if (value) {
showSlides();
} else {
blankScreen();
}
}
void OpenLPClient::changeSong(int it) { void OpenLPClient::changeSong(int it) {
auto n = QString::number(it); auto n = QString::number(it);
auto url = "/api/service/set?data=%7B%22request%22%3A+%7B%22id%22%3A+" + n + "%7D%7D&_=1627181837297"; auto url = "/api/service/set?data=%7B%22request%22%3A+%7B%22id%22%3A+" + n + "%7D%7D&_=1627181837297";
@ -81,37 +69,26 @@ void OpenLPClient::changeSlide(int slide) {
get(url); get(url);
} }
void OpenLPClient::setBaseUrl() { void OpenLPClient::get(QString urlExt) {
auto const [host, port] = getOpenLPConnectionData(); QUrl url(QString(BaseUrl) + urlExt);
m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
}
void OpenLPClient::get(QString const&urlExt) {
QUrl url(m_baseUrl + urlExt);
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
m_nam->get(rqst); m_nam->get(rqst);
} }
void OpenLPClient::post(QString const&url, QString const&data) {
QNetworkRequest rqst(QUrl(m_baseUrl + url));
rqst.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
m_nam->post(rqst, data.toUtf8());
}
void OpenLPClient::requestSongList() { void OpenLPClient::requestSongList() {
QUrl url(m_baseUrl + "/api/service/list?_=1626628079579"); QUrl url(QString(BaseUrl) + "/api/service/list?_=1626628079579");
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
m_songListNam->get(rqst); m_songListNam->get(rqst);
} }
void OpenLPClient::requestSlideList() { void OpenLPClient::requestSlideList() {
QUrl url(m_baseUrl + "/api/controller/live/text?_=1626628079579"); QUrl url(QString(BaseUrl) + "/api/controller/live/text?_=1626628079579");
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
m_slideListNam->get(rqst); m_slideListNam->get(rqst);
} }
void OpenLPClient::poll() { void OpenLPClient::poll() {
QUrl url(m_baseUrl + "/api/poll?_=1626628079579"); QUrl url(QString(BaseUrl) + "/api/poll?_=1626628079579");
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
m_pollingNam->get(rqst); m_pollingNam->get(rqst);
} }
@ -167,7 +144,7 @@ void OpenLPClient::handleSongListResponse(QNetworkReply *reply) {
auto items = doc.object()["results"].toObject()["items"].toArray(); auto items = doc.object()["results"].toObject()["items"].toArray();
m_songNameMap.clear(); m_songNameMap.clear();
m_songList.clear(); m_songList.clear();
for (auto const &item : items) { for (const auto &item : items) {
auto song = item.toObject(); auto song = item.toObject();
auto name = song["title"].toString(); auto name = song["title"].toString();
auto id = song["id"].toString(); auto id = song["id"].toString();
@ -190,12 +167,12 @@ void OpenLPClient::handleSlideListResponse(QNetworkReply *reply) {
QStringList tagList; QStringList tagList;
auto doc = QJsonDocument::fromJson(data); auto doc = QJsonDocument::fromJson(data);
auto items = doc.object()["results"].toObject()["slides"].toArray(); auto items = doc.object()["results"].toObject()["slides"].toArray();
for (auto const&item : items) { for (const auto &item : items) {
auto const slide = item.toObject(); auto slide = item.toObject();
auto text = slide["text"].toString(); auto text = slide["text"].toString();
auto tag = slide["tag"].toString(); auto tag = slide["tag"].toString();
slideList.push_back(std::move(text)); slideList.push_back(text);
tagList.push_back(std::move(tag)); tagList.push_back(tag);
} }
emit slideListUpdate(tagList, slideList); emit slideListUpdate(tagList, slideList);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -19,7 +19,11 @@ class OpenLPClient: public QObject {
Q_OBJECT Q_OBJECT
private: private:
QString m_baseUrl; struct Song {
QString name;
QString id;
};
const QString BaseUrl = QString("http://") + SlideHost + ":4316";
QNetworkAccessManager *m_nam = new QNetworkAccessManager(this); QNetworkAccessManager *m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this); QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_songListNam = new QNetworkAccessManager(this); QNetworkAccessManager *m_songListNam = new QNetworkAccessManager(this);
@ -49,18 +53,12 @@ class OpenLPClient: public QObject {
void showSlides(); void showSlides();
void setSlidesVisible(bool value);
void changeSong(int it); void changeSong(int it);
void changeSlide(int slide); void changeSlide(int slide);
void setBaseUrl();
private: private:
void get(QString const&url); void get(QString url);
void post(QString const&url, QString const&data);
void requestSongList(); void requestSongList();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

View File

@ -1 +0,0 @@
IDI_ICON1 ICON DISCARDABLE "sc9k.ico"

View File

@ -1,188 +0,0 @@
/*
* Copyright 2021 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <QSettings>
#include "consts.hpp"
#include "settingsdata.hpp"
void setVideoConfig(QSettings &settings, QVector<VideoConfig> const&vcList) {
settings.beginGroup("Camera");
settings.beginWriteArray("VideoImageConfig");
for (auto i = 0; auto const&vc : vcList) {
settings.setArrayIndex(i);
settings.setValue("brightness", vc.brightness);
settings.setValue("saturation", vc.saturation);
settings.setValue("contrast", vc.contrast);
settings.setValue("sharpness", vc.sharpness);
settings.setValue("hue", vc.hue);
++i;
}
settings.endArray();
settings.endGroup();
}
void setVideoConfig(QVector<VideoConfig> const&vcList) {
QSettings s;
setVideoConfig(s, vcList);
}
QVector<VideoConfig> getVideoConfig(QSettings &settings) {
QVector<VideoConfig> vc(MaxCameraPresets);
settings.beginGroup("Camera");
auto const size = std::min(settings.beginReadArray("VideoImageConfig"), MaxCameraPresets);
for (auto i = 0; i < size; ++i) {
settings.setArrayIndex(i);
vc[i] = {
.brightness = settings.value("brightness").toInt(),
.saturation = settings.value("saturation").toInt(),
.contrast = settings.value("contrast").toInt(),
.sharpness = settings.value("sharpness").toInt(),
.hue = settings.value("hue").toInt(),
};
}
settings.endArray();
settings.endGroup();
return vc;
}
QVector<VideoConfig> getVideoConfig() {
QSettings s;
return getVideoConfig(s);
}
void setCameraConnectionData(QSettings &settings, ConnectionData const&cd) {
settings.beginGroup("CameraClient");
settings.setValue("Host", cd.host);
settings.setValue("Port", cd.port);
settings.endGroup();
}
void setOpenLPConnectionData(QSettings &settings, ConnectionData const&cd) {
settings.beginGroup("OpenLPClient");
settings.setValue("Host", cd.host);
settings.setValue("Port", cd.port);
settings.endGroup();
}
void setOBSConnectionData(QSettings &settings, ConnectionData const&cd) {
settings.beginGroup("OBSClient");
settings.setValue("Host", cd.host);
settings.setValue("Port", cd.port);
settings.endGroup();
}
ConnectionData getCameraConnectionData(QSettings &settings) {
ConnectionData out;
settings.beginGroup("CameraClient");
out.host = settings.value("Host", "192.168.100.88").toString();
out.port = static_cast<uint16_t>(settings.value("Port", 80).toInt());
settings.endGroup();
return out;
}
ConnectionData getOpenLPConnectionData(QSettings &settings) {
ConnectionData out;
settings.beginGroup("OpenLPClient");
out.host = settings.value("Host", "127.0.0.1").toString();
out.port = static_cast<uint16_t>(settings.value("Port", 4316).toInt());
settings.endGroup();
return out;
}
ConnectionData getOBSConnectionData(QSettings &settings) {
ConnectionData out;
settings.beginGroup("OBSClient");
out.host = settings.value("Host", "127.0.0.1").toString();
out.port = static_cast<uint16_t>(settings.value("Port", 9302).toInt());
settings.endGroup();
return out;
}
void setCameraConnectionData(ConnectionData const&cd) {
QSettings settings;
settings.beginGroup("CameraClient");
settings.setValue("Host", cd.host);
settings.setValue("Port", cd.port);
settings.endGroup();
}
void setOpenLPConnectionData(ConnectionData const&cd) {
QSettings settings;
settings.beginGroup("OpenLPClient");
settings.setValue("Host", cd.host);
settings.setValue("Port", cd.port);
settings.endGroup();
}
void setOBSConnectionData(ConnectionData const&cd) {
QSettings settings;
settings.beginGroup("OBSClient");
settings.setValue("Host", cd.host);
settings.setValue("Port", cd.port);
settings.endGroup();
}
ConnectionData getCameraConnectionData() {
QSettings s;
return getCameraConnectionData(s);
}
ConnectionData getOpenLPConnectionData() {
QSettings s;
return getOpenLPConnectionData(s);
}
ConnectionData getOBSConnectionData() {
QSettings s;
return getOBSConnectionData(s);
}
void setViews(QSettings &settings, QVector<View> const&views) {
settings.beginGroup("Views");
settings.beginWriteArray("Views");
for (auto i = 0; auto const&view : views) {
settings.setArrayIndex(i);
settings.setValue("Name", view.name);
settings.setValue("Slides", view.slides);
settings.setValue("ObsSlides", view.obsSlides);
settings.setValue("Preset", view.cameraPreset);
++i;
}
settings.endArray();
settings.endGroup();
}
void setViews(QVector<View> const&views) {
QSettings s;
return setViews(s, views);
}
QVector<View> getViews(QSettings &settings) {
QVector<View> out;
settings.beginGroup("Views");
auto const size = settings.beginReadArray("Views");
for (auto i = 0; i < size; ++i) {
settings.setArrayIndex(i);
out.emplace_back(View{
.name = settings.value("Name").toString(),
.slides = settings.value("Slides").toBool(),
.obsSlides = settings.value("ObsSlides").toBool(),
.cameraPreset = settings.value("Preset").toInt(),
});
}
settings.endArray();
settings.endGroup();
return out;
}
QVector<View> getViews() {
QSettings s;
return getViews(s);
}

View File

@ -1,76 +0,0 @@
/*
* Copyright 2021 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <cstdint>
#include <QString>
#include <QVector>
struct VideoConfig {
int brightness = 6;
int saturation = 4;
int contrast = 8;
int sharpness = 3;
int hue = 7;
};
void setVideoConfig(class QSettings &settings, QVector<VideoConfig> const&vc);
void setVideoConfig(QVector<VideoConfig> const&vc);
QVector<VideoConfig> getVideoConfig(class QSettings &settings);
QVector<VideoConfig> getVideoConfig();
struct ConnectionData {
QString host;
uint16_t port = 0;
};
void setCameraConnectionData(class QSettings &settings, ConnectionData const&cd);
void setOpenLPConnectionData(class QSettings &settings, ConnectionData const&cd);
void setOBSConnectionData(class QSettings &settings, ConnectionData const&cd);
[[nodiscard]]
ConnectionData getCameraConnectionData(class QSettings &settings);
[[nodiscard]]
ConnectionData getOpenLPConnectionData(class QSettings &settings);
[[nodiscard]]
ConnectionData getOBSConnectionData(class QSettings &settings);
[[nodiscard]]
ConnectionData getCameraConnectionData();
[[nodiscard]]
ConnectionData getOpenLPConnectionData();
[[nodiscard]]
ConnectionData getOBSConnectionData();
struct View {
QString name;
bool slides = false;
bool obsSlides = false;
int cameraPreset = -1;
};
void setViews(class QSettings &settings, QVector<View> const&views);
void setViews(QVector<View> const&views);
[[nodiscard]]
QVector<View> getViews(class QSettings &settings);
[[nodiscard]]
QVector<View> getViews();

View File

@ -1,313 +0,0 @@
/*
* Copyright 2021 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <QCheckBox>
#include <QComboBox>
#include <QFormLayout>
#include <QHeaderView>
#include <QIntValidator>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSettings>
#include <QSpacerItem>
#include <QSpinBox>
#include <QTabWidget>
#include <QTableWidget>
#include <QVBoxLayout>
#include "consts.hpp"
#include "settingsdialog.hpp"
enum ViewColumn {
Name = 0,
Slides,
ObsSlides,
CameraPreset,
Count
};
SettingsDialog::SettingsDialog(QWidget *parent): QDialog(parent) {
auto const lyt = new QVBoxLayout(this);
auto const tabs = new QTabWidget(this);
lyt->addWidget(tabs);
tabs->addTab(setupViewConfig(tabs), tr("&Views"));
tabs->addTab(setupImageConfig(tabs), tr("&Image"));
tabs->addTab(setupNetworkInputs(tabs), tr("&Network"));
lyt->addWidget(setupButtons(this));
setFixedSize(440, 440);
}
QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) {
auto const root = new QWidget(parent);
auto const lyt = new QFormLayout(root);
auto const portValidator = new QIntValidator(1, 65536, this);
QSettings settings;
// camera settings
{
auto const c = getCameraConnectionData(settings);
m_cameraHostLe = new QLineEdit(root);
m_cameraPortLe = new QLineEdit(root);
m_cameraHostLe->setText(c.host);
m_cameraPortLe->setText(QString::number(c.port));
m_cameraPortLe->setValidator(portValidator);
lyt->addRow(tr("Camera &Host:"), m_cameraHostLe);
lyt->addRow(tr("Ca&mera Port:"), m_cameraPortLe);
}
// OpenLP settings
{
auto const c = getOpenLPConnectionData(settings);
m_openLpHostLe = new QLineEdit(root);
m_openLpPortLe = new QLineEdit(root);
m_openLpHostLe->setText(c.host);
m_openLpPortLe->setText(QString::number(c.port));
m_openLpPortLe->setValidator(portValidator);
lyt->addRow(tr("Op&enLP Host:"), m_openLpHostLe);
lyt->addRow(tr("Open&LP Port:"), m_openLpPortLe);
}
// OBS settings
{
auto const c = getOBSConnectionData(settings);
m_obsHostLe = new QLineEdit(root);
m_obsPortLe = new QLineEdit(root);
m_obsHostLe->setText(c.host);
m_obsPortLe->setText(QString::number(c.port));
m_obsPortLe->setValidator(portValidator);
lyt->addRow(tr("O&BS Host:"), m_obsHostLe);
lyt->addRow(tr("OB&S Port:"), m_obsPortLe);
}
return root;
}
QWidget *SettingsDialog::setupImageConfig(QWidget *parent) {
auto const root = new QWidget(parent);
auto const lyt = new QVBoxLayout(root);
{
auto const formRoot = new QWidget(parent);
auto const formLyt = new QFormLayout(formRoot);
lyt->addWidget(formRoot);
m_videoConfig = getVideoConfig();
auto const mkSb = [parent, formLyt](QString const&lbl) {
auto const s = new QSpinBox(parent);
s->setAlignment(Qt::AlignRight);
s->setRange(0, 14);
formLyt->addRow(lbl, s);
return s;
};
auto const presetNo = new QComboBox(parent);
connect(presetNo, &QComboBox::currentIndexChanged, this, &SettingsDialog::updateVidConfigPreset);
for (auto i = 0; i < MaxCameraPresets; ++i) {
presetNo->addItem(tr("Camera Preset %1").arg(i + 1));
}
formLyt->addRow(presetNo);
m_vidBrightness = mkSb(tr("&Brightness:"));
m_vidSaturation = mkSb(tr("&Saturation:"));
m_vidContrast = mkSb(tr("Con&trast:"));
m_vidSharpness = mkSb(tr("Sharpn&ess:"));
m_vidHue = mkSb(tr("&Hue:"));
updateVidConfigPreset(0);
}
{
auto const btnRoot = new QWidget(parent);
auto const btnLyt = new QHBoxLayout(btnRoot);
lyt->addWidget(btnRoot);
btnLyt->setAlignment(Qt::AlignRight);
auto const previewBtn = new QPushButton(tr("&Preview"), btnRoot);
btnLyt->addWidget(previewBtn);
connect(previewBtn, &QPushButton::clicked, this, [this] {
this->collectVideoConfig();
auto const &vc = m_videoConfig[m_vidCurrentPreset];
emit previewPreset(m_vidCurrentPreset + 1, vc);
});
}
return root;
}
QWidget *SettingsDialog::setupViewConfig(QWidget *parent) {
auto const root = new QWidget(parent);
auto const lyt = new QVBoxLayout(root);
auto const btnsRoot = new QWidget(root);
m_viewTable = new QTableWidget(root);
lyt->addWidget(btnsRoot);
lyt->addWidget(m_viewTable);
{ // table
QStringList columns;
columns.resize(ViewColumn::Count);
columns[ViewColumn::Name] = tr("Name");
columns[ViewColumn::Slides] = tr("Slides");
columns[ViewColumn::ObsSlides] = tr("OBS Slides");
columns[ViewColumn::CameraPreset] = tr("Camera Preset");
m_viewTable->setColumnCount(static_cast<int>(columns.size()));
m_viewTable->setHorizontalHeaderLabels(columns);
m_viewTable->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
m_viewTable->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
auto const hdr = m_viewTable->horizontalHeader();
m_viewTable->setColumnWidth(1, 70);
m_viewTable->setColumnWidth(2, 75);
m_viewTable->setColumnWidth(3, 70);
hdr->setStretchLastSection(true);
}
{ // add/removes buttons
auto const btnsLyt = new QHBoxLayout(btnsRoot);
auto const addBtn = new QPushButton("A&dd", btnsRoot);
auto const rmBtn = new QPushButton("&Remove", btnsRoot);
addBtn->setFixedWidth(70);
rmBtn->setFixedWidth(70);
rmBtn->setDisabled(true);
btnsLyt->addWidget(addBtn);
btnsLyt->addWidget(rmBtn);
btnsLyt->setAlignment(Qt::AlignLeft);
connect(addBtn, &QPushButton::clicked, this, [this, addBtn] {
auto const row = m_viewTable->rowCount();
m_viewTable->setRowCount(row + 1);
setupViewRow(row);
addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
});
connect(rmBtn, &QPushButton::clicked, this, [this, addBtn] {
auto const row = m_viewTable->currentRow();
m_viewTable->removeRow(row);
addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
});
connect(m_viewTable, &QTableWidget::currentCellChanged, rmBtn, [this, addBtn, rmBtn] (int row) {
rmBtn->setEnabled(row > -1 && row < m_viewTable->rowCount());
addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
});
auto const views = getViews();
m_viewTable->setRowCount(static_cast<int>(views.size()));
for (auto row = 0; auto const&view : views) {
setupViewRow(row, view);
++row;
}
}
return root;
}
QWidget *SettingsDialog::setupButtons(QWidget *parent) {
auto const root = new QWidget(parent);
auto const lyt = new QHBoxLayout(root);
m_errLbl = new QLabel(root);
auto const okBtn = new QPushButton(tr("&OK"), root);
auto const applyBtn = new QPushButton(tr("&Apply"), root);
auto const cancelBtn = new QPushButton(tr("&Cancel"), root);
lyt->addWidget(m_errLbl);
lyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
lyt->addWidget(okBtn);
lyt->addWidget(applyBtn);
lyt->addWidget(cancelBtn);
connect(okBtn, &QPushButton::clicked, this, &SettingsDialog::handleOK);
connect(applyBtn, &QPushButton::clicked, this, &SettingsDialog::handleApply);
connect(cancelBtn, &QPushButton::clicked, this, &SettingsDialog::reject);
return root;
}
int SettingsDialog::handleApply() {
QSettings settings;
QVector<View> views;
auto const viewsErr = collectViews(views);
if (viewsErr) {
return -1;
}
setViews(settings, views);
setCameraConnectionData(settings, {
.host = m_cameraHostLe->text(),
.port = m_cameraPortLe->text().toUShort(),
});
setOpenLPConnectionData(settings, {
.host = m_openLpHostLe->text(),
.port = m_openLpPortLe->text().toUShort(),
});
setOBSConnectionData(settings, {
.host = m_obsHostLe->text(),
.port = m_obsPortLe->text().toUShort(),
});
collectVideoConfig();
setVideoConfig(settings, m_videoConfig);
return 0;
}
void SettingsDialog::handleOK() {
if (handleApply() == 0) {
accept();
}
}
void SettingsDialog::setupViewRow(int row, View const&view) {
// name
auto const nameItem = new QTableWidgetItem(view.name);
m_viewTable->setItem(row, ViewColumn::Name, nameItem);
// slides
auto const slidesCb = new QCheckBox(m_viewTable);
slidesCb->setChecked(view.slides);
m_viewTable->setCellWidget(row, ViewColumn::Slides, slidesCb);
// obs slides
auto const obsSlidesCb = new QCheckBox(m_viewTable);
obsSlidesCb->setChecked(view.obsSlides);
m_viewTable->setCellWidget(row, ViewColumn::ObsSlides, obsSlidesCb);
// camera preset
auto const presetItem = new QTableWidgetItem(QString::number(view.cameraPreset));
m_viewTable->setItem(row, ViewColumn::CameraPreset, presetItem);
}
int SettingsDialog::collectViews(QVector<View> &views) const {
for (auto row = 0; row < m_viewTable->rowCount(); ++row) {
auto const viewNo = row + 1;
bool ok = false;
auto const name = m_viewTable->item(row, ViewColumn::Name)->text();
if (name.trimmed() == "") {
m_errLbl->setText(tr("View %1 has no name.").arg(viewNo));
return 1;
}
auto const cameraPreset = m_viewTable->item(row, ViewColumn::CameraPreset)->text().toInt(&ok);
if (!ok || cameraPreset < 1 || cameraPreset > MaxCameraPresets) {
m_errLbl->setText(tr("View %1 has invalid preset (1-%2)").arg(viewNo).arg(MaxCameraPresets));
return 2;
}
views.emplace_back(View{
.name = name,
.slides = dynamic_cast<QCheckBox*>(m_viewTable->cellWidget(row, ViewColumn::Slides))->isChecked(),
.obsSlides = dynamic_cast<QCheckBox*>(m_viewTable->cellWidget(row, ViewColumn::ObsSlides))->isChecked(),
.cameraPreset = cameraPreset,
});
}
return 0;
}
void SettingsDialog::collectVideoConfig() {
auto &vc = m_videoConfig[m_vidCurrentPreset];
auto constexpr getVal = [](int &val, QSpinBox *src) {
if (src) {
val = src->value();
}
};
getVal(vc.brightness, m_vidBrightness);
getVal(vc.saturation, m_vidSaturation);
getVal(vc.contrast, m_vidContrast);
getVal(vc.sharpness, m_vidSharpness);
getVal(vc.hue, m_vidHue);
}
void SettingsDialog::updateVidConfigPreset(int preset) {
// update to new value
auto constexpr setVal = [](int val, QSpinBox *dst) {
if (dst) {
dst->setValue(val);
}
};
auto const&vc = m_videoConfig[preset];
setVal(vc.brightness, m_vidBrightness);
setVal(vc.saturation, m_vidSaturation);
setVal(vc.contrast, m_vidContrast);
setVal(vc.sharpness, m_vidSharpness);
setVal(vc.hue, m_vidHue);
m_vidCurrentPreset = preset;
}
void SettingsDialog::updateVidConfigPresetCollect(int preset) {
collectVideoConfig();
updateVidConfigPreset(preset);
}

View File

@ -1,56 +0,0 @@
/*
* Copyright 2021 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <QDialog>
#include "consts.hpp"
#include "settingsdata.hpp"
class SettingsDialog: public QDialog {
Q_OBJECT
private:
QVector<VideoConfig> m_videoConfig = QVector<VideoConfig>(MaxCameraPresets);
class QLabel *m_errLbl = nullptr;
class QLineEdit *m_cameraHostLe = nullptr;
class QLineEdit *m_cameraPortLe = nullptr;
class QLineEdit *m_openLpHostLe = nullptr;
class QLineEdit *m_openLpPortLe = nullptr;
class QLineEdit *m_obsHostLe = nullptr;
class QLineEdit *m_obsPortLe = nullptr;
class QSpinBox *m_vidBrightness = nullptr;
class QSpinBox *m_vidSaturation = nullptr;
class QSpinBox *m_vidContrast = nullptr;
class QSpinBox *m_vidSharpness = nullptr;
class QSpinBox *m_vidHue = nullptr;
int m_vidCurrentPreset = 0;
class QTableWidget *m_viewTable = nullptr;
public:
explicit SettingsDialog(QWidget *parent);
private:
QWidget *setupNetworkInputs(QWidget *parent);
QWidget *setupViewConfig(QWidget *parent);
QWidget *setupImageConfig(QWidget *parent);
QWidget *setupButtons(QWidget *parent);
[[nodiscard]]
int handleApply();
void handleOK();
void setupViewRow(int row, View const&view = {});
/**
* Gets views from table.
* @return error code
*/
[[nodiscard("Must check error code")]]
int collectViews(QVector<View> &views) const;
void collectVideoConfig();
void updateVidConfigPreset(int preset);
void updateVidConfigPresetCollect(int preset);
signals:
void previewPreset(int, VideoConfig const&);
};

View File

@ -1,22 +1,22 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include <QComboBox>
#include <QDebug> #include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView> #include <QHeaderView>
#include <QListWidget>
#include <QTableWidget> #include <QTableWidget>
#include <QVBoxLayout>
#include "slideview.hpp" #include "slideview.hpp"
SlideView::SlideView(QWidget *parent): QWidget(parent) { SlideView::SlideView(QWidget *parent): QWidget(parent) {
auto lyt = new QHBoxLayout(this); auto lyt = new QVBoxLayout(this);
m_songSelector = new QListWidget(this); m_songSelector = new QComboBox(this);
m_slideTable = new QTableWidget(this); m_slideTable = new QTableWidget(this);
auto header = m_slideTable->horizontalHeader(); auto header = m_slideTable->horizontalHeader();
header->setVisible(false); header->setVisible(false);
@ -28,29 +28,24 @@ SlideView::SlideView(QWidget *parent): QWidget(parent) {
#ifndef _WIN32 #ifndef _WIN32
m_slideTable->setAlternatingRowColors(true); m_slideTable->setAlternatingRowColors(true);
#endif #endif
lyt->addWidget(m_slideTable);
lyt->addWidget(m_songSelector); lyt->addWidget(m_songSelector);
lyt->addWidget(m_slideTable);
connect(m_slideTable, &QTableWidget::currentCellChanged, this, &SlideView::slideChanged); connect(m_slideTable, &QTableWidget::currentCellChanged, this, &SlideView::slideChanged);
} }
QString SlideView::getNextSong() const { QString SlideView::getNextSong() const {
auto const cnt = m_songSelector->count(); const auto cnt = m_songSelector->count();
auto const idx = m_songSelector->currentRow() + 1; const auto idx = m_songSelector->currentIndex() + 1;
if (idx < cnt) { if (idx < cnt) {
return m_songSelector->currentItem()->text(); return m_songSelector->itemText(idx);
} }
return ""; return "";
} }
void SlideView::pollUpdate(QString const&songName, int slide) { void SlideView::pollUpdate(QString songName, int slide) {
auto const songItems = m_songSelector->findItems(songName, Qt::MatchFixedString); if (songName != m_currentSong) {
if (songItems.empty()) {
return;
}
auto songItem = songItems.first();
if (songItem != m_songSelector->currentItem()) {
m_currentSong = songName; m_currentSong = songName;
m_songSelector->setCurrentItem(songItem); m_songSelector->setCurrentText(songName);
} }
if (slide != m_currentSlide) { if (slide != m_currentSlide) {
m_currentSlide = slide; m_currentSlide = slide;
@ -59,23 +54,16 @@ void SlideView::pollUpdate(QString const&songName, int slide) {
} }
void SlideView::changeSong(int song) { void SlideView::changeSong(int song) {
if (song < 0) { if (m_songSelector->currentText() != m_currentSong) {
return;
}
auto const songItem = m_songSelector->item(song);
if (songItem && songItem->text() != m_currentSong) {
emit songChanged(song); emit songChanged(song);
} }
} }
void SlideView::slideListUpdate(QStringList tagList, QStringList const&slideList) { void SlideView::slideListUpdate(QStringList tagList, QStringList slideList) {
for (auto &tag : tagList) {
tag = tag.split("").join("\n");
}
m_currentSlide = 0; m_currentSlide = 0;
m_slideTable->setRowCount(static_cast<int>(slideList.size())); m_slideTable->setRowCount(slideList.size());
for (int i = 0; i < slideList.size(); ++i) { for (int i = 0; i < slideList.size(); ++i) {
auto const& txt = slideList[i]; auto txt = slideList[i];
auto item = new QTableWidgetItem(txt); auto item = new QTableWidgetItem(txt);
item->setFlags(item->flags() & ~Qt::ItemIsEditable); item->setFlags(item->flags() & ~Qt::ItemIsEditable);
m_slideTable->setItem(i, 0, item); m_slideTable->setItem(i, 0, item);
@ -91,16 +79,16 @@ void SlideView::reset() {
m_currentSlide = -1; m_currentSlide = -1;
} }
void SlideView::songListUpdate(QStringList const&songList) { void SlideView::songListUpdate(QStringList songList) {
// Is this replacing an existing song list or is it the initial song list? // Is this replacing an existing song list or is it the initial song list?
// We want to reset the song to 0 upon replacement, // We want to reset the song to 0 upon replacement,
// but leave it alone upon initialization. // but leave it alone upon initialization.
auto isReplacement = m_songSelector->count() > 0; auto isReplacement = m_songSelector->count() > 0;
disconnect(m_songSelector, &QListWidget::currentRowChanged, this, &SlideView::changeSong); disconnect(m_songSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSong(int)));
m_songSelector->clear(); m_songSelector->clear();
m_songSelector->addItems(songList); m_songSelector->addItems(songList);
if (isReplacement) { if (isReplacement) {
changeSong(0); changeSong(0);
} }
connect(m_songSelector, &QListWidget::currentRowChanged, this, &SlideView::changeSong); connect(m_songSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSong(int)));
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2024 gary@drinkingtea.net * Copyright 2021 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -13,22 +13,20 @@ class SlideView: public QWidget {
Q_OBJECT Q_OBJECT
private: private:
class QTableWidget *m_slideTable = nullptr; class QTableWidget *m_slideTable = nullptr;
class QListWidget *m_songSelector = nullptr; class QComboBox *m_songSelector = nullptr;
QString m_currentSong; QString m_currentSong;
int m_currentSlide = -1; int m_currentSlide = -1;
public: public:
explicit SlideView(QWidget *parent = nullptr); explicit SlideView(QWidget *parent = nullptr);
[[nodiscard]]
QString getNextSong() const; QString getNextSong() const;
public slots: public slots:
void pollUpdate(const QString& songId, int slideNum); void pollUpdate(QString songId, int slideNum);
void songListUpdate(QStringList const&songList); void songListUpdate(QStringList songList);
void slideListUpdate(QStringList tagList, QStringList const&songList); void slideListUpdate(QStringList tagList, QStringList songList);
void reset(); void reset();