Compare commits

...

10 Commits

32 changed files with 1888 additions and 219 deletions

View File

@@ -2,7 +2,7 @@
source:
- src
copyright_notice: |-
Copyright 2018 gtalent2@gmail.com
Copyright 2018 - 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

View File

@@ -1,16 +1,14 @@
cmake_minimum_required(VERSION 2.8.8)
cmake_minimum_required(VERSION 3.8.8)
project(bullock)
project(Bullock)
set(QTDIR "" CACHE STRING "Path to Qt Libraries")
include(deps/buildcore/base.cmake)
set(CMAKE_PREFIX_PATH ${QTDIR})
find_package(Qt5 COMPONENTS Core Network Widgets REQUIRED)
find_package(Qt6 COMPONENTS Core Network Widgets REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
include(address_sanitizer)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@@ -23,31 +21,23 @@ endif()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare")
if (CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# forces colored output when using ninja
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
endif()
if(APPLE)
set(CMAKE_MACOSX_RPATH OFF)
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/bullock")
set(BULLOCK_DIST_BIN bullock.app/Contents/MacOS)
set(BULLOCK_DIST_LIB bullock.app/Contents/Library)
set(BULLOCK_DIST_PLUGIN bullock.app/Contents/Plugins)
set(BULLOCK_DIST_RESOURCES bullock.app/Contents/Resources)
set(BULLOCK_DIST_MAC_APP_CONTENTS bullock.app/Contents)
set(CMAKE_INSTALL_NAME_DIR "@executable_path/../Library/Bullock")
set(BULLOCK_DIST_BIN Bullock.app/Contents/MacOS)
set(BULLOCK_DIST_LIB Bullock.app/Contents/Library)
set(BULLOCK_DIST_MODULE Bullock.app/Contents/Plugins)
set(BULLOCK_DIST_RESOURCES Bullock.app/Contents/Resources)
set(BULLOCK_DIST_MAC_APP_CONTENTS Bullock.app/Contents)
else()
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../lib/bullock")
if(NOT ${QTDIR} STREQUAL "")
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib")
endif()
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../lib/Bullock")
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
set(BULLOCK_DIST_BIN bin)
set(BULLOCK_DIST_LIB lib)
@@ -56,6 +46,11 @@ endif()
enable_testing()
add_subdirectory($ENV{OX_PATH} $ENV{OX_PATH})
include_directories(
SYSTEM
$ENV{OX_PATH}/src
)
add_subdirectory(src)
install(

View File

@@ -1,82 +1,17 @@
OS=$(shell uname | tr [:upper:] [:lower:])
HOST_ENV=${OS}-$(shell uname -m)
DEVENV=devenv$(shell pwd | sed 's/\//-/g')
DEVENV_IMAGE=bullock-devenv
ifneq ($(shell which gmake 2> /dev/null),)
MAKE=gmake -s
PROJECT_NAME=Bullock
BUILDCORE_PATH=deps/buildcore
VCPKG_PKGS=
include ${BUILDCORE_PATH}/base.mk
ifeq ($(OS),darwin)
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/${PROJECT_NAME}.app/Contents/MacOS/${PROJECT_NAME}
else
MAKE=make -s
endif
ifneq ($(shell which docker 2> /dev/null),)
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
endif
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/bin/${PROJECT_NAME}
endif
make:
${ENV_RUN} ./scripts/run-make build
gba-pkg:
${ENV_RUN} ./scripts/run-make build install
${ENV_RUN} ./scripts/run-make build
${ENV_RUN} ./scripts/gba-pkg
preinstall:
${ENV_RUN} ./scripts/run-make build preinstall
install:
${ENV_RUN} ./scripts/run-make build install
clean:
${ENV_RUN} ./scripts/run-make build clean
purge:
${ENV_RUN} rm -rf build
test:
${ENV_RUN} ./scripts/run-make build test
.PHONY: run
run: install
./dist/current/bin/bullock
devenv-image:
docker build . -t ${DEVENV_IMAGE}
devenv-create:
docker run -d \
-e LOCAL_USER_ID=$(shell id -u ${USER}) \
-e DISPLAY=$(DISPLAY) \
-e QT_AUTO_SCREEN_SCALE_FACTOR=1 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v /run/dbus/:/run/dbus/ \
-v $(shell pwd):/usr/src/project \
-v /dev/shm:/dev/shm \
--restart=always \
--name ${DEVENV} \
-t ${DEVENV_IMAGE} bash
devenv-destroy:
docker rm -f ${DEVENV}
shell:
${ENV_RUN} bash
release:
${ENV_RUN} rm -rf build/${HOST_ENV}-release
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} release
debug:
${ENV_RUN} rm -rf build/${HOST_ENV}-debug
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} debug
asan:
${ENV_RUN} rm -rf build/${HOST_ENV}-asan
${ENV_RUN} ./scripts/setup-build ${HOST_ENV} asan
windows:
${ENV_RUN} rm -rf build/windows
${ENV_RUN} ./scripts/setup-build windows
windows-debug:
${ENV_RUN} rm -rf build/windows
${ENV_RUN} ./scripts/setup-build windows debug
gba:
${ENV_RUN} rm -rf build/gba-release
${ENV_RUN} ./scripts/setup-build gba release
gba-debug:
${ENV_RUN} rm -rf build/gba-debug
${ENV_RUN} ./scripts/setup-build gba debug
${PROJECT_EXECUTABLE}
.PHONY: debug
debug: install
${DEBUGGER} ${PROJECT_EXECUTABLE}

4
deps/buildcore/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
scripts/__pycache__
CMakeLists.txt.user
Session.vim
graph_info.json

373
deps/buildcore/LICENSE vendored Normal file
View File

@@ -0,0 +1,373 @@
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.

63
deps/buildcore/base.cmake vendored Normal file
View File

@@ -0,0 +1,63 @@
set(QTDIR "" CACHE PATH "Path to Qt Libraries")
set(BUILDCORE_TARGET "Native" CACHE STRING "The type of build to produce(Native/GBA)")
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules)
include(GenerateExportHeader)
include(address_sanitizer)
set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/dist/${BUILDCORE_BUILD_CONFIG}")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# enable ccache
if(NOT DEFINED ENV{BUILDCORE_SUPPRESS_CCACHE})
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}")
endif()
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG)
else()
add_definitions(-DNDEBUG)
if(APPLE)
set(CMAKE_OSX_ARCHITECTURES arm64;x86_64)
endif()
endif()
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:preprocessor")
else()
# forces colored output when using ninja
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
# enable warnings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-align")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wdouble-promotion")
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} -Wnon-virtual-dtor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-null-dereference")
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} -Wpedantic")
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} -Wunused")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused-variable")
# release build options
if (CMAKE_BUILD_TYPE STREQUAL "Release")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
endif()
enable_testing()

173
deps/buildcore/base.mk vendored Normal file
View File

@@ -0,0 +1,173 @@
#
# 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/.
#
ifeq (${OS},Windows_NT)
SHELL := powershell.exe
.SHELLFLAGS := -NoProfile -Command
OS=windows
HOST_ENV=${OS}
else
OS=$(shell uname | tr [:upper:] [:lower:])
HOST_ENV=${OS}-$(shell uname -m)
endif
DEVENV=devenv$(shell pwd | sed 's/\//-/g')
DEVENV_IMAGE=${PROJECT_NAME}-devenv
ifneq ($(shell which docker 2> /dev/null),)
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
endif
endif
ifneq ($(shell ${ENV_RUN} which python3 2> /dev/null),)
PYTHON3=python3
else
ifeq ($(shell ${ENV_RUN} python -c 'import sys; print(sys.version_info[0])'),3)
PYTHON3=python
endif
endif
SCRIPTS=${BUILDCORE_PATH}/scripts
SETUP_BUILD=${PYTHON3} ${SCRIPTS}/setup-build.py
PYBB=${PYTHON3} ${SCRIPTS}/pybb.py
CMAKE_BUILD=${PYBB} cmake-build
GET_ENV=${PYBB} getenv
CTEST=${PYBB} ctest-all
RM_RF=${PYBB} rm
HOST=$(shell ${PYBB} hostname)
BUILDCORE_HOST_SPECIFIC_BUILDPATH=$(shell ${GET_ENV} BUILDCORE_HOST_SPECIFIC_BUILDPATH)
ifneq (${BUILDCORE_HOST_SPECIFIC_BUILDPATH},)
BUILD_PATH=build/${HOST}
else
BUILD_PATH=build
endif
ifdef USE_VCPKG
ifndef VCPKG_DIR_BASE
VCPKG_DIR_BASE=.vcpkg
endif
ifndef VCPKG_VERSION
VCPKG_VERSION=2020.06
endif
VCPKG_TOOLCHAIN=--toolchain=${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
endif
ifeq ($(OS),darwin)
DEBUGGER=lldb --
else
DEBUGGER=gdb --args
endif
VCPKG_DIR=$(VCPKG_DIR_BASE)/$(VCPKG_VERSION)-$(HOST_ENV)
CURRENT_BUILD=$(HOST_ENV)-$(shell ${ENV_RUN} ${PYBB} cat .current_build)
.PHONY: build
build:
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH}
.PHONY: install
install:
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} install
.PHONY: clean
clean:
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} clean
.PHONY: purge
purge:
${ENV_RUN} ${RM_RF} .current_build
${ENV_RUN} ${RM_RF} ${BUILD_PATH}
${ENV_RUN} ${RM_RF} dist
.PHONY: test
test: build
${ENV_RUN} mypy ${SCRIPTS}
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} test
.PHONY: test-verbose
test-verbose: build
${ENV_RUN} ${CTEST} ${BUILD_PATH} --output-on-failure
.PHONY: test-rerun-verbose
test-rerun-verbose: build
${ENV_RUN} ${CTEST} ${BUILD_PATH} --rerun-failed --output-on-failure
.PHONY: devenv-image
devenv-image:
docker build . -t ${DEVENV_IMAGE}
.PHONY: devenv-create
devenv-create:
docker run -d \
-e LOCAL_USER_ID=$(shell id -u ${USER}) \
-e DISPLAY=$(DISPLAY) \
-e QT_AUTO_SCREEN_SCALE_FACTOR=1 \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-v /run/dbus/:/run/dbus/ \
-v $(shell pwd):/usr/src/project \
-v /dev/shm:/dev/shm \
--restart=always \
--name ${DEVENV} \
-t ${DEVENV_IMAGE} bash
.PHONY: devenv-destroy
devenv-destroy:
docker rm -f ${DEVENV}
ifdef ENV_RUN
.PHONY: devenv-shell
devenv-shell:
${ENV_RUN} bash
endif
ifdef USE_VCPKG
.PHONY: vcpkg
vcpkg: ${VCPKG_DIR} vcpkg-install
${VCPKG_DIR}:
${ENV_RUN} ${RM_RF} ${VCPKG_DIR}
${ENV_RUN} mkdir -p ${VCPKG_DIR_BASE}
${ENV_RUN} git clone -b release --depth 1 --branch ${VCPKG_VERSION} https://github.com/microsoft/vcpkg.git ${VCPKG_DIR}
ifneq (${OS},windows)
${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.sh
else
${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.bat
endif
.PHONY: vcpkg-install
vcpkg-install:
ifneq (${OS},windows)
${VCPKG_DIR}/vcpkg install ${VCPKG_PKGS}
else
${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS}
endif
else ifdef USE_CONAN # USE_VCPKG ################################################
.PHONY: setup-conan
conan-config:
${ENV_RUN} conan profile new ${PROJECT_NAME} --detect --force
ifeq ($(OS),linux)
${ENV_RUN} conan profile update settings.compiler.libcxx=libstdc++11 ${PROJECT_NAME}
else
${ENV_RUN} conan profile update settings.compiler.cppstd=20 ${PROJECT_NAME}
ifeq ($(OS),windows)
${ENV_RUN} conan profile update settings.compiler.runtime=static ${PROJECT_NAME}
endif
endif
.PHONY: conan
conan:
${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME}
endif # USE_VCPKG ###############################################
.PHONY: configure-xcode
configure-xcode:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 --build_root=${BUILD_PATH}
.PHONY: configure-release
configure-release:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=release --build_root=${BUILD_PATH}
.PHONY: configure-debug
configure-debug:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BUILD_PATH}
.PHONY: configure-asan
configure-asan:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BUILD_PATH}

View File

@@ -0,0 +1,18 @@
set(CMAKE_SYSTEM_NAME Windows)
set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)
# cross compilers to use for C and C++
set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}-gcc)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++)
set(CMAKE_RC_COMPILER ${TOOLCHAIN_PREFIX}-windres)
# target environment on the build host system
# set 1st to dir with the cross compiler's C/C++ headers/libs
set(CMAKE_FIND_ROOT_PATH /usr/${TOOLCHAIN_PREFIX})
# modify default behavior of FIND_XXX() commands to
# search for headers/libs in the target environment and
# search for programs in the build host environment
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

View File

@@ -0,0 +1,52 @@
# This file belongs Nick Overdijk, and is from https://github.com/NickNick/wubwubcmake
# The MIT License (MIT)
#
# Copyright (c) 2013 Nick Overdijk
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.option(USE_ASAN "Enable Address Sanitizer, if your compiler supports it" ON)
option(USE_ASAN "Enable Address Sanitizer, if your compiler supports it" OFF)
if(USE_ASAN)
include(CheckCXXSourceCompiles)
# If the compiler understands -fsanitize=address, add it to the flags (gcc since 4.8 & clang since version 3.2)
set(CMAKE_REQUIRED_FLAGS_BAK "${CMAKE_REQUIRED_FLAGS}")
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -fsanitize=address")
CHECK_CXX_SOURCE_COMPILES("int main() { return 0; }" FLAG_FSANA_SUPPORTED)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS_BAK}")
if(FLAG_FSANA_SUPPORTED)
set(asan_flag "-fsanitize=address")
else(FLAG_FSANA_SUPPORTED)
# Alternatively, try if it understands -faddress-sanitizer (clang until version 3.2)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -faddress-sanitizer")
CHECK_CXX_SOURCE_COMPILES("int main() { return 0; }" FLAG_FASAN_SUPPORTED)
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS_BAK}")
if(FLAG_FASAN_SUPPORTED)
set(asan_flag "-faddress-sanitizer")
endif(FLAG_FASAN_SUPPORTED)
endif(FLAG_FSANA_SUPPORTED)
if(FLAG_FSANA_SUPPORTED OR FLAG_FASAN_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${asan_flag}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${asan_flag}")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${asan_flag}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${asan_flag}")
endif()
endif(USE_ASAN)

28
deps/buildcore/scripts/file_to_c.py vendored Executable file
View File

@@ -0,0 +1,28 @@
#! /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)

139
deps/buildcore/scripts/pybb.py vendored Executable file
View File

@@ -0,0 +1,139 @@
#! /usr/bin/env python3
#
# 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/.
#
# "Python Busy Box" - adds cross-platform equivalents to Unix commands that
# don't translate well to that other operating system
import os
import platform
import shutil
import subprocess
import sys
from typing import List, Optional
def mkdir(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):
if (os.path.exists(path) or os.path.islink(path)) and not os.path.isdir(path):
os.remove(path)
elif os.path.isdir(path):
shutil.rmtree(path)
def ctest_all() -> int:
base_path = sys.argv[2]
if not os.path.isdir(base_path):
# no generated projects
return 0
args = ['ctest'] + sys.argv[3:]
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:
if not os.path.isdir(base_path):
# nothing to build
return 0
for d in os.listdir(base_path):
args = ['cmake', '--build', os.path.join(base_path, d)]
if target is not None:
args.extend(['--target', target])
err = subprocess.run(args).returncode
if err != 0:
return err
return 0
def conan() -> int:
project_name = sys.argv[2]
conan_dir = '.conanbuild'
err = 0
try:
mkdir(conan_dir)
except:
return 1
if err != 0:
return err
args = ['conan', 'install', '../', '--build=missing', '-pr', project_name]
os.chdir(conan_dir)
err = subprocess.run(args).returncode
if err != 0:
return err
return 0
def cat(paths: List[str]) -> int:
for path in paths:
try:
with open(path) as f:
data = f.read()
sys.stdout.write(data)
except FileNotFoundError:
sys.stderr.write('cat: {}: no such file or directory\n'.format(path))
return 1
sys.stdout.write('\n')
return 0
def get_env(var_name: str) -> int:
if var_name not in os.environ:
return 1
sys.stdout.write(os.environ[var_name])
return 0
def hostname() -> int:
sys.stdout.write(platform.node())
return 0
def main() -> int:
err = 0
if sys.argv[1] == 'mkdir':
try:
mkdir(sys.argv[2])
except:
err = 1
elif sys.argv[1] == 'rm':
for i in range(2, len(sys.argv)):
rm(sys.argv[i])
elif sys.argv[1] == 'conan-install':
err = conan()
elif sys.argv[1] == 'ctest-all':
err = ctest_all()
elif sys.argv[1] == 'cmake-build':
err = cmake_build(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None)
elif sys.argv[1] == 'cat':
err = cat(sys.argv[2:])
elif sys.argv[1] == 'getenv':
err = get_env(sys.argv[2])
elif sys.argv[1] == 'hostname':
err = hostname()
else:
sys.stderr.write('Command not found\n')
err = 1
return err
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit(1)

101
deps/buildcore/scripts/setup-build.py vendored Executable file
View File

@@ -0,0 +1,101 @@
#! /usr/bin/env python3
#
# 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 argparse
import os
import platform
import shutil
import subprocess
import sys
from pybb import mkdir, rm
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('--target', help='Platform target',
default='{:s}-{:s}'.format(sys.platform, platform.machine()))
parser.add_argument('--build_type', 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 of build directories (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()
if args.build_type == 'asan':
build_type_arg = 'Debug'
sanitizer_status = 'ON'
elif args.build_type == 'debug':
build_type_arg = 'Debug'
sanitizer_status = 'OFF'
elif args.build_type == 'release':
build_type_arg = 'Release'
sanitizer_status = 'OFF'
else:
print('Error: Invalid build tool')
return 1
if args.build_tool == 'xcode':
build_config = '{:s}-{:s}'.format(args.target, args.build_tool)
else:
build_config = '{:s}-{:s}'.format(args.target, args.build_type)
if 'QTDIR' in os.environ:
qt_path = '-DQTDIR={:s}'.format(os.environ['QTDIR'])
else:
qt_path = ''
if args.build_tool == '' or args.build_tool == 'default':
if shutil.which('ninja') is None:
build_tool = ''
else:
build_tool = '-GNinja'
elif args.build_tool == 'xcode':
build_tool = '-GXcode'
else:
print('Error: Invalid build tool')
return 1
project_dir = os.getcwd()
build_dir = '{:s}/{:s}/{:s}'.format(project_dir, args.build_root, build_config)
rm(build_dir)
cmake_cmd = [
'cmake', '-S', project_dir, '-B', build_dir, build_tool,
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
'-DCMAKE_TOOLCHAIN_FILE={:s}'.format(args.toolchain),
'-DCMAKE_BUILD_TYPE={:s}'.format(build_type_arg),
'-DUSE_ASAN={:s}'.format(sanitizer_status),
'-DBUILDCORE_BUILD_CONFIG={:s}'.format(build_config),
'-DBUILDCORE_TARGET={:s}'.format(args.target),
]
if qt_path != '':
cmake_cmd.append(qt_path)
if platform.system() == 'Windows':
cmake_cmd.append('-A x64')
subprocess.run(cmake_cmd)
mkdir('dist')
if int(args.current_build) != 0:
cb = open('.current_build', 'w')
cb.write(args.build_type)
cb.close()
rm('compile_commands.json')
if platform.system() != 'Windows':
os.symlink('{:s}/compile_commands.json'.format(build_dir), 'compile_commands.json')
return 0
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit(1)

View File

@@ -1,13 +1,15 @@
project(bullock)
project(Bullock)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKe_INCLUDE_CURRENT_DIR ON)
find_package(Qt5Widgets)
find_pacKage(Qt6Widgets)
set(CMAKE_AUTOMOC ON)
add_executable(
bullock MACOSX_BUNDLE
Bullock MACOSX_BUNDLE
callstackmodel.cpp
channelview.cpp
channelmodel.cpp
main.cpp
mainwindow.cpp
processdata.cpp
@@ -18,19 +20,21 @@ add_executable(
)
target_link_libraries(
bullock
Qt5::Core
Qt5::Network
Qt5::Widgets
Bullock
OxMetalClaw
OxStd
Qt6::Core
Qt6::Network
Qt6::Widgets
)
if(APPLE)
set_target_properties(bullock PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
set_target_properties(Bullock PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
endif()
install(
TARGETS
bullock
Bullock
RUNTIME DESTINATION
${BULLOCK_DIST_BIN}
BUNDLE DESTINATION .

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -10,11 +10,11 @@
#include "callstackmodel.hpp"
int CallStackModel::rowCount(const QModelIndex &parent) const {
int CallStackModel::rowCount(const QModelIndex&) const {
return m_frames.size();
}
int CallStackModel::columnCount(const QModelIndex &parent) const {
int CallStackModel::columnCount(const QModelIndex&) const {
return Column::End;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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

198
src/channelmodel.cpp Normal file
View File

@@ -0,0 +1,198 @@
/*
* Copyright 2018 - 2023 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 <algorithm>
#include "channelmodel.hpp"
QVariant ChannelModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case ColEnabled:
return tr("On");
case ColName:
return tr("Name");
case ColCount:
return tr("Messages");
case ColChildCount:
return tr("Child Messages");
}
}
return {};
}
Qt::ItemFlags ChannelModel::flags(const QModelIndex &index) const {
if (!index.isValid()) {
return {};
}
auto out = QAbstractItemModel::flags(index);
switch (index.column()) {
case ColEnabled:
out |= Qt::ItemIsUserCheckable | Qt::ItemIsEditable;
break;
}
return out;
}
bool ChannelModel::setData(const QModelIndex &index, const QVariant &val, int role) {
const auto item = static_cast<Channel*>(index.internalPointer());
switch (role) {
case Qt::EditRole:
return false;
case Qt::CheckStateRole:
switch (index.column()) {
case ColEnabled: {
item->setEnabled(val.toBool());
emit dataChanged(index, index);
emit m_procData->channelToggled();
return item->on;
}
}
break;
}
return QAbstractItemModel::setData(index, val, role);
}
QVariant ChannelModel::data(const QModelIndex &index, int role) const {
if (!index.isValid()) {
return {};
}
const auto item = static_cast<Channel*>(index.internalPointer());
switch (role) {
case Qt::CheckStateRole:
switch (index.column()) {
case ColEnabled:
return item->on ? Qt::Checked : Qt::Unchecked;
}
break;
case Qt::TextAlignmentRole:
switch (index.column()) {
case ColChildCount:
case ColCount:
return Qt::AlignRight;
}
break;
case Qt::DisplayRole:
switch (index.column()) {
case ColName:
return item->name();
case ColCount:
return QString("%L2").arg(item->msgCnt);
case ColChildCount:
return QString("%L2").arg(item->msgCnt + item->childrenMsgCnt());
}
}
return {};
}
QModelIndex ChannelModel::index(int row, int column, const QModelIndex &parent) const {
if (!hasIndex(row, column, parent)) {
return {};
}
const auto parentItem = parent.isValid() ?
static_cast<Channel*>(parent.internalPointer()) :
m_rootCh;
const auto childItem = parentItem->children[row].get();
if (childItem) {
return createIndex(row, column, childItem);
}
return {};
}
QModelIndex ChannelModel::parent(const QModelIndex &index) const {
if (!index.isValid()) {
return {};
}
const auto item = static_cast<Channel*>(index.internalPointer());
const auto parentItem = item->parent;
if (parentItem == m_rootCh) {
return {};
}
return createIndex(parentItem->row(), 0, parentItem);
}
int ChannelModel::rowCount(const QModelIndex &parent) const {
Channel *ch = nullptr;
if (parent.isValid()) {
ch = static_cast<Channel*>(parent.internalPointer());
} else {
ch = m_rootCh;
}
if (!ch) {
return 0;
}
return ch->children.size();
}
int ChannelModel::columnCount(const QModelIndex&) const {
return ChannelColumnCnt;
}
Channel *ChannelModel::createChannel(Channel *ch, const QStringList &name, int it) {
if (it == name.size()) {
return ch;
}
const auto lvlName = name.sliced(0, it + 1).join(ChannelSplitter);
auto childIt = std::find_if(ch->children.begin(), ch->children.end(), [&](auto &val) {
return val->fullName() == lvlName;
});
Channel *child = nullptr;
if (childIt != ch->children.end()) {
child = (*childIt).get();
} else {
const auto childId = getChannelId(lvlName);
const auto insertPt = std::find_if(ch->children.begin(), ch->children.end(), [lvlName](const auto &b) {
return b->name() >= lvlName;
}).offset();
const auto chRow = ch->row();
const auto parIdx = m_rootCh == ch ? QModelIndex{} : createIndex(chRow, 0, ch);
beginInsertRows(parIdx, insertPt, 0);
child = (*ch->children.emplace(insertPt, ox::make_unique<Channel>(childId, ch))).get();
m_procData->channels[childId].channel = child;
endInsertRows();
}
return createChannel(child, name, it + 1);
}
void ChannelModel::addChannel(ChId chId) {
addChannelInternal(chId);
}
void ChannelModel::incChannel(ChId chId) {
auto ch = m_procData->channels[chId].channel;
++ch->msgCnt;
const auto idx = createIndex(ch->row(), 0, ch);
emit dataChanged({}, idx);
}
void ChannelModel::setProcessData(ProcessData *data) {
if (m_procData) {
disconnect(m_procData, &ProcessData::channelAdded, this, &ChannelModel::addChannel);
disconnect(m_procData, &ProcessData::channelInc, this, &ChannelModel::incChannel);
}
m_procData = data;
beginResetModel();
m_rootCh = &m_procData->rootChannel;
if (m_procData) {
for (auto i = 0l; i < m_procData->traceEvents.size(); ++i) {
addChannelInternal(m_procData->traceEvents[i]._channel);
}
connect(m_procData, &ProcessData::channelAdded, this, &ChannelModel::addChannel);
connect(m_procData, &ProcessData::channelInc, this, &ChannelModel::incChannel);
}
endResetModel();
}
Channel *ChannelModel::addChannelInternal(ChId chId) {
const auto &chName = getChannelFullName(chId);
auto namePath = chName.split(ChannelSplitter);
if (namePath.size() == 0) {
namePath = {chName};
}
return createChannel(m_rootCh, namePath);
}

69
src/channelmodel.hpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* Copyright 2018 - 2023 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 <QAbstractItemModel>
#include "processdata.hpp"
enum ChannelColumn {
ColEnabled = 0,
ColName,
ColCount,
ColChildCount,
ChannelColumnCnt,
};
class ChannelModel: public QAbstractItemModel {
Q_OBJECT
private:
Channel *m_rootCh = nullptr;
ProcessData *m_procData = nullptr;
public:
ChannelModel() = default;
[[nodiscard]]
bool setData(const QModelIndex &index, const QVariant &val, int role) override;
[[nodiscard]]
QVariant data(const QModelIndex &index, int role) const override;
[[nodiscard]]
Qt::ItemFlags flags(const QModelIndex &index) const override;
[[nodiscard]]
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
[[nodiscard]]
QModelIndex index(int row, int column,
const QModelIndex &parent) const override;
[[nodiscard]]
QModelIndex parent(const QModelIndex &index) const override;
[[nodiscard]]
int rowCount(const QModelIndex &parent) const override;
[[nodiscard]]
int columnCount(const QModelIndex &parent) const override;
void addChannel(ChId chId);
void incChannel(ChId chId);
void setProcessData(ProcessData *data);
private:
Channel *createChannel(Channel *ch, const QStringList &name, int it = 0);
Channel *addChannelInternal(ChId chId);
};

76
src/channelview.cpp Normal file
View File

@@ -0,0 +1,76 @@
/*
* Copyright 2018 - 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/.
*/
#include <QHeaderView>
#include <QLabel>
#include <QTreeView>
#include <QSettings>
#include <QVBoxLayout>
#include "channelview.hpp"
ChannelView::ChannelView(QWidget *parent): QWidget(parent) {
const auto lyt = new QVBoxLayout(this);
m_tree = new QTreeView(this);
m_model = new ChannelModel;
m_tree->setModel(m_model);
lyt->addWidget(new QLabel(tr("Channels"), this));
lyt->addWidget(m_tree);
m_tree->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
m_tree->setTreePosition(ChannelColumn::ColName);
readState();
}
ChannelView::~ChannelView() {
writeState();
}
void ChannelView::logChEntry(ChId chId) {
++m_channels[chId]->msgCnt;
}
bool ChannelView::channelOn(ChId chId) const {
return m_channels[chId]->on;
}
Channel *ChannelView::findChannel(const QStringList &path, const ox::Vector<ox::UPtr<Channel>> &channels, int it) {
if (it >= path.size()) {
return nullptr;
}
const auto &name = path[it];
for (const auto &ch : channels) {
if (name == ch->fullName()) {
if (it == path.size() - 1) {
return ch.get();
} else {
return findChannel(path, ch->children, it + 1);
}
}
}
return nullptr;
}
void ChannelView::setProcessData(ProcessData *data) {
m_model->setProcessData(data);
//m_frameTableModel->clear();
//m_fieldView->clear();
}
void ChannelView::readState() {
QSettings settings;
settings.beginGroup("Logger.ChannelView");
m_tree->header()->restoreState(settings.value("headers").toByteArray());
settings.endGroup();
}
void ChannelView::writeState() {
QSettings settings;
settings.beginGroup("Logger.ChannelView");
settings.setValue("headers", m_tree->header()->saveState());
settings.endGroup();
}

54
src/channelview.hpp Normal file
View File

@@ -0,0 +1,54 @@
/*
* Copyright 2018 - 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/.
*/
#pragma once
#include <QVector>
#include <QWidget>
#include "callstackmodel.hpp"
#include "channelmodel.hpp"
#include "traceeventmodel.hpp"
#pragma once
class ChannelView: public QWidget {
Q_OBJECT
private:
int m_selectedChannel = 0;
class QTreeView *m_tree = nullptr;
// maps channel ids to the channels
QVector<Channel*> m_channels;
QVector<Channel*> m_channelModelRoot;
QHash<QString, Channel*> m_channelMap;
ChannelModel *m_model = nullptr;
public:
explicit ChannelView(QWidget *parent = nullptr);
~ChannelView() override;
void addChannel(ChId chId);
void logChEntry(ChId chId);
[[nodiscard]]
bool channelOn(ChId chId) const;
[[nodiscard]]
Channel *findChannel(const QStringList &path, const ox::Vector<ox::UPtr<Channel>> &channels, int it = 0);
void setProcessData(ProcessData *data);
private:
void readState();
void writeState();
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -13,9 +13,9 @@
int main(int argc, char **args) {
QApplication app(argc, args);
app.setApplicationName("Bullock");
app.setOrganizationName("DrinkingTea");
app.setOrganizationDomain("drinkingtea.net");
QApplication::setApplicationName("Bullock");
QApplication::setOrganizationName("DrinkingTea");
QApplication::setOrganizationDomain("drinkingtea.net");
LogServer server;
@@ -24,5 +24,5 @@ int main(int argc, char **args) {
QObject::connect(&server, &LogServer::newDataFeed, &w, &MainWindow::addDataFeed);
return app.exec();
return QApplication::exec();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -9,13 +9,14 @@
#include <QAction>
#include <QApplication>
#include <QDateTime>
#include <QDesktopWidget>
#include <QHBoxLayout>
#include <QMenuBar>
#include <QScreen>
#include <QSettings>
#include <QSplitter>
#include "channelview.hpp"
#include "mainwindow.hpp"
MainWindow::MainWindow() {
@@ -32,12 +33,14 @@ MainWindow::MainWindow() {
auto leftPane = new QWidget(this);
auto leftPaneSplitter = new QSplitter(Qt::Vertical, leftPane);
m_channelView = new ChannelView(this);
leftPaneSplitter->addWidget(m_channelView);
m_procSelector = new ProcessSelector(leftPaneSplitter);
connect(m_procSelector, &ProcessSelector::selectionChanged, this, &MainWindow::setProcess);
leftPaneSplitter->addWidget(m_procSelector);
m_splitter->addWidget(leftPaneSplitter);
m_traceView = new TraceView(m_splitter);
m_traceView = new TraceView(m_channelView, m_splitter);
m_splitter->addWidget(m_traceView);
m_splitter->setStretchFactor(1, 3);
@@ -64,6 +67,8 @@ void MainWindow::readState() {
QSettings settings;
settings.beginGroup("MainWindow");
m_splitter->restoreState(settings.value("splitterState").toByteArray());
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("state").toByteArray());
settings.endGroup();
}
@@ -71,6 +76,8 @@ void MainWindow::writeState() {
QSettings settings;
settings.beginGroup("MainWindow");
settings.setValue("splitterState", m_splitter->saveState());
settings.setValue("geometry", saveGeometry());
settings.setValue("state", saveState());
settings.endGroup();
}
@@ -81,6 +88,7 @@ void MainWindow::setProcess(QString procKey) {
m_currentProc = nullptr;
}
m_traceView->setProcessData(m_currentProc);
m_channelView->setProcessData(m_currentProc);
}
void MainWindow::setupMenu() {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -23,6 +23,7 @@ class MainWindow: public QMainWindow {
ProcessData *m_currentProc = nullptr;
ProcessSelector *m_procSelector = nullptr;
TraceView *m_traceView = nullptr;
ChannelView *m_channelView = nullptr;
public:
MainWindow();

View File

@@ -1,51 +1,193 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 2023 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 <QDebug>
#include <ox/std/assert.hpp>
#include <ox/std/algorithm.hpp>
#include <QHash>
#include <QJsonArray>
#include <QSettings>
#include "processdata.hpp"
Field::Field(QJsonObject field) {
this->name = field["name"].toString();
this->type = field["type"].toString();
this->value = field["value"].toString();
auto fields = field["fields"].toArray();
for (auto field : fields) {
this->fields.push_back(field.toObject());
static ChId channelIdIt = 0;
static QHash<QString, ChId> channelToId;
struct ChannelData {
QString fullName;
QString name;
ChId parent = -1;
ChannelData(const QString &pFullName, ChId pParent) {
fullName = pFullName;
parent = pParent;
const auto lastSep = fullName.lastIndexOf(ChannelSplitter);
if (lastSep < 0) {
name = fullName;
} else {
name = fullName.mid(lastSep + ox_strlen(ChannelSplitter));
}
}
};
static QVector<ChannelData> idToChannel = {{"", -1}};
static void addChannel(const QString &ch, ChId parent = -1) {
const auto chPath = ch.split(ChannelSplitter);
if (chPath.empty()) {
return;
}
const auto add = [](const QString &c, ChId parent) {
if (!channelToId.contains(c)) {
channelToId.emplace(c, ++channelIdIt);
idToChannel.emplace_back(c, parent);
}
return channelToId[c];
};
auto current = chPath.first();
add(current, parent);
for (auto i = 1; i < chPath.size(); ++i) {
const auto &segment = chPath[i];
current += ChannelSplitter + segment;
parent = add(current, parent);
}
}
Frame::Frame(QJsonObject frame) {
[[nodiscard]]
const QString &getChannelFullName(ChId id) {
return idToChannel[id].fullName;
}
[[nodiscard]]
const QString &getChannelName(ChId id) {
return idToChannel[id].name;
}
[[nodiscard]]
ChId getChannelId(const QString &ch) {
if (!channelToId.contains(ch)) [[unlikely]] {
addChannel(ch);
return channelIdIt;
}
return channelToId.value(ch);
}
[[nodiscard]]
ChId getChannelParentId(ChId id) {
return idToChannel[id].parent;
}
Channel::Channel(ChId pId, Channel *pParent) {
id = pId;
parent = pParent;
QSettings settings;
if (!fullName().isEmpty()) {
settings.beginGroup("Bullock.Channel.Enabled");
on = settings.value(fullName(), true).toBool();
settings.endGroup();
}
}
int Channel::childrenMsgCnt() const noexcept {
auto out = 0;
for (const auto &c : children) {
out += c->msgCnt + c->childrenMsgCnt();
}
return out;
}
bool Channel::showMsgs() const noexcept {
return this->on && (!parent || parent->showMsgs());
}
const QString &Channel::name() const {
return getChannelName(id);
}
const QString &Channel::fullName() const {
return getChannelFullName(id);
}
int Channel::row() const noexcept {
if (!parent) [[unlikely]] {
return 0;
}
for (auto i = 0; const auto &c : parent->children) {
if (c.get() == this) {
return i;
}
++i;
}
return -1;
}
void Channel::setEnabled(bool val) noexcept {
on = val;
QSettings settings;
settings.beginGroup("Bullock.Channel.Enabled");
settings.setValue(fullName(), val);
settings.endGroup();
}
[[nodiscard]]
bool Channel::enabled() const noexcept {
return (parent == nullptr || parent->enabled()) && on;
}
Field::Field(const QJsonObject &field) {
this->name = field["name"].toString();
this->type = field["type"].toString();
this->value = field["value"].toString();
const auto fields = field["fields"].toArray();
for (const auto &field : fields) {
this->fields.emplace_back(field.toObject());
}
}
Frame::Frame(const QJsonObject &frame) {
this->arch = frame["arch"].toString();
this->function = frame["function"].toString();
this->file = frame["file"].toString();
this->line = frame["line"].toDouble();
auto fields = frame["fields"].toArray();
for (auto field : fields) {
this->fields.push_back(field.toObject());
const auto fields = frame["fields"].toArray();
for (const auto &field : fields) {
this->fields.emplace_back(field.toObject());
}
}
TraceEvent::TraceEvent(QJsonObject tp) {
this->channel = tp["channel"].toString();
TraceEvent::TraceEvent(const QJsonObject &tp) {
this->_channel = getChannelId(tp["channel"].toString());
this->logMsg = tp["log_msg"].toString();
auto frames = tp["frames"].toArray();
for (auto frame : frames) {
this->frames.push_back(frame.toObject());
this->_file = tp["file"].toString();
this->_line = tp["line"].toInt();
const auto frames = tp["frames"].toArray();
for (const auto &frame : frames) {
this->frames.emplace_back(frame.toObject());
}
}
QString TraceEvent::file() const {
return this->frames[0].file;
TraceEvent::TraceEvent(const ox::trace::TraceMsgRcv &tm) {
this->_channel = getChannelId(tm.ch.c_str());
this->logMsg = tm.msg.c_str();
this->_file = tm.file.c_str();
this->_line = tm.line;
this->time = tm.time;
}
int TraceEvent::line() const {
return this->frames[0].line;
const QString &TraceEvent::channel() const noexcept {
return idToChannel[_channel].fullName;
}
const QString &TraceEvent::file() const noexcept {
return this->_file;
}
int TraceEvent::line() const noexcept {
return this->_line;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -8,18 +8,36 @@
#pragma once
#include <ox/std/trace.hpp>
#include <QJsonObject>
#include <QObject>
#include <QStringList>
#include <QVector>
using ChId = int;
constexpr auto ChannelSplitter = "::";
[[nodiscard]]
const QString &getChannelFullName(ChId id);
[[nodiscard]]
const QString &getChannelName(ChId id);
[[nodiscard]]
ChId getChannelId(const QString &ch);
[[nodiscard]]
ChId getChannelParentId(ChId id);
struct Field {
QString name;
QString type;
QString value;
QVector<Field> fields;
Field(QJsonObject field = {});
Field(const QJsonObject &field = {});
};
@@ -30,33 +48,89 @@ struct Frame {
int line = 0;
QVector<Field> fields;
Frame(QJsonObject frame = {});
Frame(const QJsonObject &field = {});
};
struct TraceEvent {
QString channel;
uint64_t time = 0;
int _channel = 0;
QString logMsg;
QVector<Frame> frames;
QString _file;
int _line = 0;
TraceEvent(QJsonObject tp = {});
TraceEvent() = default;
QString file() const;
TraceEvent(const QJsonObject &tp);
int line() const;
TraceEvent(const ox::trace::TraceMsgRcv &tm);
[[nodiscard]]
const QString &channel() const noexcept;
[[nodiscard]]
const QString &file() const noexcept;
[[nodiscard]]
int line() const noexcept;
};
struct Channel {
Channel *parent = nullptr;
ChId id = -1;
int msgCnt = 0;
bool on = true;
ox::Vector<ox::UPtr<Channel>> children;
Channel(ChId pId, Channel *pParent);
[[nodiscard]]
int childrenMsgCnt() const noexcept;
[[nodiscard]]
bool showMsgs() const noexcept;
[[nodiscard]]
const QString &name() const;
[[nodiscard]]
const QString &fullName() const;
[[nodiscard]]
int row() const noexcept;
void setEnabled(bool val) noexcept;
[[nodiscard]]
bool enabled() const noexcept;
};
struct ProcessData: public QObject {
Q_OBJECT
public:
struct ChannelEntry {
// the initialization of channel should not be used to track whether
// or not a process has yet received a given channel
bool present = false;
Channel *channel = nullptr;
};
QString procKey;
QVector<TraceEvent> traceEvents;
QVector<ChannelEntry> channels;
Channel rootChannel = Channel(0, nullptr);
signals:
/**
* Emitted whenever a new TraceEvent is added.
* Emits the index of the TraceEvent
*/
void traceEvent(const TraceEvent&);
void traceEvent(std::size_t);
void channelAdded(int);
void channelInc(int);
void channelToggled();
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -8,11 +8,18 @@
#include <QDebug>
#include <ox/mc/read.hpp>
#include <ox/std/trace.hpp>
#include "server.hpp"
DataFeed::DataFeed(QIODevice *dev): QObject(dev) {
DataFeed::DataFeed(QIODevice *dev, bool skipInit): QObject(dev) {
m_dev = dev;
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
if (!skipInit) {
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
} else {
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
}
}
const QSharedPointer<ProcessData> &DataFeed::procData() {
@@ -20,33 +27,122 @@ const QSharedPointer<ProcessData> &DataFeed::procData() {
}
void DataFeed::handleInit() {
auto doc = QJsonDocument::fromJson(m_dev->readLine());
if (doc.isObject()) {
auto msg = doc.object();
if (msg["type"].toString() == "Init") {
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
const auto init = [&] {
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::handleInit);
connect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
};
ox::trace::MsgId peekChar;
m_dev->peek(reinterpret_cast<char*>(&peekChar), 1);
if (peekChar == ox::trace::MsgId::Json) {
auto doc = QJsonDocument::fromJson(m_dev->readLine());
if (doc.isObject()) {
auto msg = doc.object();
if (msg["type"].toString() == "Init") {
init();
}
}
} else if (peekChar == ox::trace::MsgId::Init) {
ox::Array<char, 5> hdrBuff;
m_dev->peek(hdrBuff.data(), hdrBuff.size());
const auto msgSize = *reinterpret_cast<uint32_t*>(&hdrBuff[1]);
while (m_dev->bytesAvailable() < msgSize && m_dev->isOpen()) {
m_dev->waitForBytesWritten(1);
}
m_dev->skip(5);
auto msgBuff = ox_malloca(msgSize, char);
m_dev->read(msgBuff.get(), msgSize);
const auto [msg, err] = ox::readMC<ox::trace::InitTraceMsgRcv>(msgBuff.get(), msgSize);
if (err) [[unlikely]] {
qDebug().noquote() << "Bad message";
return;
}
init();
}
}
void DataFeed::read() {
while (m_dev->bytesAvailable()) {
const auto doc = QJsonDocument::fromJson(m_dev->readLine());
if (m_procData) {
const auto msg = doc.object();
if (msg["type"] == "TraceEvent") {
const auto te = msg["data"].toObject();
m_procData->traceEvents.push_back(te);
emit m_procData->traceEvent(m_procData->traceEvents.last());
while (m_dev && m_dev->bytesAvailable()) {
ox::trace::MsgId msgId;
m_dev->peek(reinterpret_cast<char*>(&msgId), 1);
if (msgId == ox::trace::MsgId::Init && !m_dev->isOpen()) {
qInfo() << "Connection closed";
break;
} else if (msgId == ox::trace::MsgId::TraceEvent) {
if (m_dev->bytesAvailable() > 5) {
if (!handleMcTraceEvent()) {
break;
}
}
} else if (msgId == ox::trace::MsgId::Json) {
const auto json = m_dev->readLine();
const auto doc = QJsonDocument::fromJson(json);
if (m_procData) {
const auto msg = doc.object();
if (msg["type"] == "TraceEvent") {
addTraceEvent(msg["data"].toObject());
} else if (msg["type"] == "Init") {
qInfo() << "Connection closed";
endFeed();
break;
} else {
qDebug().noquote() << "Bad message:" << json;
}
}
} else {
qDebug().noquote() << "Bad message id:" << static_cast<int>(msgId);
qDebug() << "Connection is in invalid state, ending.";
m_dev->close();
m_dev->deleteLater();
}
}
}
bool DataFeed::handleMcTraceEvent() {
ox::Array<char, 5> hdrBuff;
m_dev->peek(hdrBuff.data(), hdrBuff.size());
const auto msgSize = *reinterpret_cast<uint32_t*>(&hdrBuff[1]);
if (m_dev->bytesAvailable() < msgSize) {
return false;
}
m_dev->skip(5);
auto msgBuff = ox_malloca(msgSize, char);
m_dev->read(msgBuff.get(), msgSize);
const auto [msg, err] = ox::readMC<ox::trace::TraceMsgRcv>(msgBuff.get(), msgSize);
if (err) [[unlikely]] {
qDebug().noquote() << "Bad message";
return true;
}
addTraceEvent(msg);
return true;
}
void DataFeed::endFeed() {
disconnect(m_dev, &QIODevice::readyRead, this, &DataFeed::read);
emit feedEnd(m_dev);
m_dev = nullptr;
}
static ProcessData::ChannelEntry &chEntry(auto *list, ChId id) {
if (id >= list->size()) {
list->resize(id + 1);
}
return (*list)[id];
}
void DataFeed::addTraceEvent(TraceEvent teSrc) {
const auto &te = m_procData->traceEvents.emplace_back(std::move(teSrc));
auto &ce = chEntry(&m_procData->channels, te._channel);
if (!ce.present) {
ce.present = true;
emit m_procData->channelAdded(te._channel);
}
emit m_procData->channelInc(te._channel);
emit m_procData->traceEvent(m_procData->traceEvents.size() - 1);
}
LogServer::LogServer() {
m_server->listen(QHostAddress::LocalHost, 5590);
m_server->listen(QHostAddress::Any, 5590);
connect(m_server, &QTcpServer::newConnection, this, &LogServer::handleConnection);
}
@@ -54,5 +150,13 @@ void LogServer::handleConnection() {
auto conn = m_server->nextPendingConnection();
connect(conn, &QAbstractSocket::disconnected, conn, &QObject::deleteLater);
connect(this, &QObject::destroyed, conn, &QObject::deleteLater);
emit newDataFeed(new DataFeed(conn));
auto feed = new DataFeed(conn);
connect(feed, &DataFeed::feedEnd, this, &LogServer::setupDataFeed);
emit newDataFeed(feed);
}
void LogServer::setupDataFeed(QIODevice *conn) {
auto feed = new DataFeed(conn, true);
connect(feed, &DataFeed::feedEnd, this, &LogServer::setupDataFeed);
emit newDataFeed(feed);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -26,14 +26,30 @@ class DataFeed: public QObject {
QIODevice *m_dev = nullptr;
public:
DataFeed(QIODevice *dev);
/**
* Constructor
* @param dev typically a TCP connection, but could be any QIODevice
* @param skipInit indicates that the feed should not expect an init message
*/
explicit DataFeed(QIODevice *dev, bool skipInit = false);
const QSharedPointer<ProcessData> &procData();
public slots:
void handleInit();
void read();
private:
// Returns true if read complete, false if there is incomplete data.
bool handleMcTraceEvent();
void endFeed();
void addTraceEvent(TraceEvent teSrc);
signals:
void feedEnd(QIODevice*);
};
class LogServer: public QObject {
@@ -45,9 +61,11 @@ class LogServer: public QObject {
public:
LogServer();
public slots:
void handleConnection();
void setupDataFeed(QIODevice *conn);
signals:
void newDataFeed(DataFeed*);
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -8,13 +8,18 @@
#include <QDebug>
#include "channelview.hpp"
#include "traceeventmodel.hpp"
int TraceEventModel::rowCount(const QModelIndex &parent) const {
return m_traceEvents.size();
TraceEventModel::TraceEventModel(ChannelView *cv) {
m_channelView = cv;
}
int TraceEventModel::columnCount(const QModelIndex &parent) const {
int TraceEventModel::rowCount(const QModelIndex&) const {
return m_visibleTraceEvents.size();
}
int TraceEventModel::columnCount(const QModelIndex&) const {
return Column::End;
}
@@ -43,16 +48,16 @@ QVariant TraceEventModel::data(const QModelIndex &index, int role) const {
return QVariant();
}
if (index.row() >= m_traceEvents.size() || index.row() < 0) {
if (index.row() >= m_visibleTraceEvents.size() || index.row() < 0) {
return QVariant();
}
if (role == Qt::DisplayRole) {
const auto &te = m_traceEvents[index.row()];
const auto eventId = m_visibleTraceEvents[index.row()];
const auto &te = m_procData->traceEvents[eventId];
switch (index.column()) {
case Column::Channel:
return te.channel;
return te.channel();
case Column::Source:
return QString("%1:%2").arg(te.file()).arg(te.line());
case Column::Message:
@@ -65,31 +70,51 @@ QVariant TraceEventModel::data(const QModelIndex &index, int role) const {
}
void TraceEventModel::setProcessData(ProcessData *data) {
beginResetModel();
m_traceEvents.clear();
if (m_procData) {
disconnect(m_procData, &ProcessData::traceEvent, this, &TraceEventModel::addEvent);
disconnect(m_procData, &ProcessData::channelToggled, this, &TraceEventModel::resetChannels);
}
m_procData = data;
if (m_procData) {
for (const auto &te : m_procData->traceEvents) {
m_traceEvents.push_back(te);
}
connect(m_procData, &ProcessData::traceEvent, this, &TraceEventModel::addEvent);
connect(m_procData, &ProcessData::channelToggled, this, &TraceEventModel::resetChannels);
resetChannels();
}
}
const TraceEvent &TraceEventModel::traceEvent(int row) {
return m_procData->traceEvents[m_visibleTraceEvents[row]];
}
void TraceEventModel::addEvent(std::size_t idx) {
const auto &te = m_procData->traceEvents[idx];
const auto &ch = m_procData->channels[te._channel];
if (ch.channel->enabled()) {
addVisibleEvent(idx);
}
}
void TraceEventModel::addVisibleEvent(std::size_t idx) {
auto index = m_procData->traceEvents.size();
beginInsertRows(QModelIndex(), index, index);
m_visibleTraceEvents.push_back(idx);
endInsertRows();
}
void TraceEventModel::clearVisibleEvents() {
m_visibleTraceEvents.clear();
}
void TraceEventModel::resetChannels() {
beginResetModel();
m_visibleTraceEvents.clear();
for (auto i = 0l; i < m_procData->traceEvents.size(); ++i) {
const auto &te = m_procData->traceEvents[i];
const auto &ch = m_procData->channels[te._channel];
if (ch.channel->enabled()) {
m_visibleTraceEvents.push_back(i);
}
}
endResetModel();
}
TraceEvent TraceEventModel::traceEvent(int row) {
if (m_procData) {
return m_procData->traceEvents[row];
}
return {};
}
void TraceEventModel::addEvent(const TraceEvent &event) {
auto index = m_traceEvents.size();
beginInsertRows(QModelIndex(), index, index);
m_traceEvents.push_back(event);
endInsertRows();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -25,10 +25,14 @@ class TraceEventModel: public QAbstractTableModel {
};
private:
QVector<TraceEvent> m_traceEvents;
using TraceEventIdx = int;
QVector<TraceEventIdx> m_visibleTraceEvents;
ProcessData *m_procData = nullptr;
class ChannelView *m_channelView = nullptr;
public:
explicit TraceEventModel(class ChannelView *cv);
int rowCount(const QModelIndex &parent = QModelIndex()) const override ;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@@ -39,9 +43,17 @@ class TraceEventModel: public QAbstractTableModel {
void setProcessData(ProcessData *data);
TraceEvent traceEvent(int row);
[[nodiscard]]
const TraceEvent &traceEvent(int row);
public slots:
void addEvent(const TraceEvent &event);
void addEvent(std::size_t idx);
private:
void addVisibleEvent(std::size_t idx);
void clearVisibleEvents();
void resetChannels();
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -33,7 +33,8 @@ static QTreeWidgetItem *treeItem(const Field &field, QTreeWidget *treeWidget, QT
return item;
}
TraceView::TraceView(QWidget *parent): QWidget(parent) {
TraceView::TraceView(class ChannelView *cv, QWidget *parent): QWidget(parent) {
m_channelView = cv;
auto lyt = new QHBoxLayout;
setLayout(lyt);
@@ -41,14 +42,14 @@ TraceView::TraceView(QWidget *parent): QWidget(parent) {
lyt->addWidget(m_splitter);
m_eventTable = new QTableView(this);
m_model = new TraceEventModel;
m_model = new TraceEventModel(cv);
m_eventTable->setModel(m_model);
m_eventTable->horizontalHeader()->setStretchLastSection(true);
m_eventTable->verticalHeader()->hide();
m_eventTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_eventTable->setSelectionMode(QAbstractItemView::SingleSelection);
connect(m_eventTable->selectionModel(), &QItemSelectionModel::selectionChanged,
[this](const QItemSelection &selected, const QItemSelection &deselected) {
[this](const QItemSelection &selected, const QItemSelection&) {
m_frameTableModel->clear();
m_fieldView->clear();
auto indexes = selected.indexes();
@@ -100,6 +101,7 @@ void TraceView::readState() {
m_eventTable->horizontalHeader()->restoreGeometry(settings.value("eventTableGeometry").toByteArray());
m_frameTable->horizontalHeader()->restoreState(settings.value("frameTableState").toByteArray());
m_frameTable->horizontalHeader()->restoreGeometry(settings.value("frameTableGeometry").toByteArray());
m_frameTable->setAlternatingRowColors(true);
m_fieldView->header()->restoreState(settings.value("fieldViewState").toByteArray());
m_fieldView->header()->restoreGeometry(settings.value("fieldViewGeometry").toByteArray());
m_splitter->restoreState(settings.value("splitterState").toByteArray());
@@ -127,7 +129,7 @@ void TraceView::setProcessData(ProcessData *data) {
m_fieldView->clear();
}
void TraceView::handleFrameSelection(const QItemSelection &selected, const QItemSelection &deselected) {
void TraceView::handleFrameSelection(const QItemSelection &selected, const QItemSelection&) {
auto indexes = selected.indexes();
m_fieldView->clear();
if (indexes.size()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2018 gtalent2@gmail.com
* Copyright 2018 - 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
@@ -30,11 +30,12 @@ class TraceView: public QWidget {
QSplitter *m_lowerSplitter = nullptr;
CallStackModel *m_frameTableModel = nullptr;
TraceEventModel *m_model = nullptr;
class ChannelView *m_channelView = nullptr;
public:
TraceView(QWidget *parent = nullptr);
explicit TraceView(class ChannelView *cv, QWidget *parent = nullptr);
~TraceView();
~TraceView() override;
void setProcessData(ProcessData *data);