diff --git a/.github/workflows/release.yml b/.github/workflows/build.yml
similarity index 67%
rename from .github/workflows/release.yml
rename to .github/workflows/build.yml
index 4c45bc1..665697a 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/build.yml
@@ -1,13 +1,16 @@
-name: Release
+name: Build and Release
on:
push:
branches:
- - main
+ - '**'
+ tags:
+ - 'v*'
+ pull_request:
jobs:
build-linux:
- name: Build Linux (.deb)
+ name: Build Linux
runs-on: ubuntu-latest
steps:
- name: Checkout code
@@ -31,10 +34,16 @@ jobs:
run: |
mkdir build
cd build
- cmake .. -DCMAKE_BUILD_TYPE=Release
+ if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
+ BUILD_TYPE="Release"
+ else
+ BUILD_TYPE="Debug"
+ fi
+ cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
make -j$(nproc)
- name: Package with CPack
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
cd build
cpack -G DEB
@@ -44,9 +53,10 @@ jobs:
with:
name: linux-deb
path: build/*.deb
+ if-no-files-found: ignore
build-macos:
- name: Build macOS (.dmg)
+ name: Build macOS
runs-on: macos-latest
steps:
- name: Checkout code
@@ -54,22 +64,28 @@ jobs:
- name: Install dependencies
run: |
- brew install sdl2 sdl2_ttf dylibbundler create-dmg
+ brew install sdl2 sdl2_ttf pkg-config dylibbundler create-dmg
- name: Configure and Build
run: |
mkdir build
cd build
- # We prefer static SDL2 and SDL2_ttf if possible, as per CMakeLists.txt
- cmake .. -DCMAKE_BUILD_TYPE=Release
+ if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
+ BUILD_TYPE="Release"
+ else
+ BUILD_TYPE="Debug"
+ fi
+ cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
make -j$(sysctl -n hw.ncpu)
- name: Create App Bundle
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
chmod +x bundle_macos.sh
BUILD_DIR="build" ./bundle_macos.sh
- name: Create DMG
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
mkdir -p dist
create-dmg \
@@ -88,27 +104,48 @@ jobs:
with:
name: macos-dmg
path: dist/*.dmg
+ if-no-files-found: ignore
build-windows:
- name: Build Windows (.zip)
+ name: Build Windows
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
+ - name: Initialize vcpkg
+ shell: pwsh
+ run: |
+ cd $env:VCPKG_INSTALLATION_ROOT
+ git fetch --unshallow --tags
+ git checkout master
+ git pull
+
- name: Configure
shell: pwsh
run: |
+ if ("${{ github.ref }}" -eq "refs/heads/main") {
+ $build_type = "Release"
+ } else {
+ $build_type = "Debug"
+ }
cmake -S . -B build `
- -DCMAKE_BUILD_TYPE=Release `
+ -DCMAKE_BUILD_TYPE=$build_type `
-DUSE_FFMPEG=OFF `
-DVCPKG_TARGET_TRIPLET=x64-windows `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake"
- name: Build
- run: cmake --build build --config Release --parallel
+ run: |
+ if ("${{ github.ref }}" -eq "refs/heads/main") {
+ $config = "Release"
+ } else {
+ $config = "Debug"
+ }
+ cmake --build build --config $config --parallel
- name: Package
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
shell: pwsh
run: |
$dist = "dist/P2ProViewer-Windows"
@@ -122,10 +159,12 @@ jobs:
with:
name: windows-zip
path: dist/P2ProViewer-windows.zip
+ if-no-files-found: ignore
release:
name: Release
needs: [build-linux, build-macos, build-windows]
+ if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-latest
steps:
- name: Checkout code
diff --git a/.gitignore b/.gitignore
index d4828ce..04a65d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ initial_tests
__pycache__
cmake-build-debug
build
+.idea
\ No newline at end of file
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..b350233
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+P2ProViewer
\ No newline at end of file
diff --git a/.idea/P2Pro-Viewer.iml b/.idea/P2Pro-Viewer.iml
new file mode 100644
index 0000000..4c94235
--- /dev/null
+++ b/.idea/P2Pro-Viewer.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000..02b915b
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..9a1c684
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8de0f79..e98b947 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,17 +35,16 @@ find_package(SDL2_ttf CONFIG REQUIRED)
set(SDL2_LIBRARIES SDL2::SDL2 SDL2::SDL2main)
set(SDL2_TTF_LIBRARIES SDL2_ttf::SDL2_ttf)
-# Try pkg-config for libusb and ffmpeg
-find_package(PkgConfig QUIET)
-if(PKG_CONFIG_FOUND)
- if(NOT APPLE AND NOT WIN32)
- pkg_check_modules(LIBUSB libusb-1.0)
- if(USE_FFMPEG)
- pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libswscale libavutil)
+ # Try pkg-config for libusb and ffmpeg
+ find_package(PkgConfig QUIET)
+ if(PKG_CONFIG_FOUND)
+ if(NOT APPLE AND NOT WIN32)
+ pkg_check_modules(LIBUSB libusb-1.0)
+ if(USE_FFMPEG)
+ pkg_check_modules(FFMPEG REQUIRED libavcodec libavformat libswscale libavutil)
+ endif()
endif()
endif()
- # pkg_check_modules(SDL2_TTF SDL2_ttf) # Commented out to avoid polluting SDL2_TTF variables
-endif()
if(WIN32)
# On Windows with vcpkg, libusb-1.0 and ffmpeg are found via find_package or find_library
@@ -144,18 +143,21 @@ else ()
)
endif ()
-if (APPLE)
+if(APPLE)
# Prefer static linking for SDL2 and SDL2_ttf on macOS to make it more portable
# We force the search for static libraries for these specific components
set(ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
# Use pkg-config to find static versions and their dependencies if possible
- pkg_check_modules(SDL2_STATIC_PKG QUIET SDL2 STATIC IMPORTED_TARGET)
- pkg_check_modules(SDL2_TTF_STATIC_PKG QUIET SDL2_ttf STATIC IMPORTED_TARGET)
+ # Note: We don't use IMPORTED_TARGET here to avoid issues with missing dependencies in the project environment
+ pkg_check_modules(SDL2_STATIC_PKG QUIET SDL2 STATIC)
+ pkg_check_modules(SDL2_TTF_STATIC_PKG QUIET SDL2_ttf STATIC)
- if(TARGET PkgConfig::SDL2_TTF_STATIC_PKG)
- target_link_libraries(P2ProViewer PkgConfig::SDL2_TTF_STATIC_PKG)
+ if(SDL2_TTF_STATIC_PKG_FOUND)
+ target_link_libraries(P2ProViewer ${SDL2_TTF_STATIC_PKG_LIBRARIES})
+ target_link_directories(P2ProViewer PRIVATE ${SDL2_TTF_STATIC_PKG_LIBRARY_DIRS})
+ target_include_directories(P2ProViewer PRIVATE ${SDL2_TTF_STATIC_PKG_INCLUDE_DIRS})
elseif(TARGET SDL2_ttf::SDL2_ttf-static)
target_link_libraries(P2ProViewer SDL2_ttf::SDL2_ttf-static)
else()
@@ -167,9 +169,11 @@ if (APPLE)
endif()
endif()
- if(NOT TARGET PkgConfig::SDL2_TTF_STATIC_PKG)
- if(TARGET PkgConfig::SDL2_STATIC_PKG)
- target_link_libraries(P2ProViewer PkgConfig::SDL2_STATIC_PKG)
+ if(NOT SDL2_TTF_STATIC_PKG_FOUND)
+ if(SDL2_STATIC_PKG_FOUND)
+ target_link_libraries(P2ProViewer ${SDL2_STATIC_PKG_LIBRARIES})
+ target_link_directories(P2ProViewer PRIVATE ${SDL2_STATIC_PKG_LIBRARY_DIRS})
+ target_include_directories(P2ProViewer PRIVATE ${SDL2_STATIC_PKG_INCLUDE_DIRS})
elseif(TARGET SDL2::SDL2-static)
target_link_libraries(P2ProViewer SDL2::SDL2-static)
else()
@@ -191,6 +195,7 @@ if (APPLE)
find_library(AUDIOUNIT_FRAMEWORK AudioUnit)
find_library(FORCEFEEDBACK_FRAMEWORK ForceFeedback)
find_library(GAMECONTROLLER_FRAMEWORK GameController)
+ find_library(AUDIOTOOLBOX_FRAMEWORK AudioToolbox)
target_link_libraries(P2ProViewer
${METAL_FRAMEWORK}
${QUARTZCORE_FRAMEWORK}
@@ -200,30 +205,36 @@ if (APPLE)
${AUDIOUNIT_FRAMEWORK}
${FORCEFEEDBACK_FRAMEWORK}
${GAMECONTROLLER_FRAMEWORK}
- )
-
- # Link against required frameworks for SDL2 static
- find_library(METAL_FRAMEWORK Metal)
- find_library(QUARTZCORE_FRAMEWORK QuartzCore)
- find_library(COCOA_FRAMEWORK Cocoa)
- find_library(CARBON_FRAMEWORK Carbon)
- find_library(COREAUDIO_FRAMEWORK CoreAudio)
- find_library(AUDIOUNIT_FRAMEWORK AudioUnit)
- find_library(FORCEFEEDBACK_FRAMEWORK ForceFeedback)
- find_library(GAMECONTROLLER_FRAMEWORK GameController)
- target_link_libraries(P2ProViewer
- ${METAL_FRAMEWORK}
- ${QUARTZCORE_FRAMEWORK}
- ${COCOA_FRAMEWORK}
- ${CARBON_FRAMEWORK}
- ${COREAUDIO_FRAMEWORK}
- ${AUDIOUNIT_FRAMEWORK}
- ${FORCEFEEDBACK_FRAMEWORK}
- ${GAMECONTROLLER_FRAMEWORK}
+ ${AUDIOTOOLBOX_FRAMEWORK}
+ "-framework CoreHaptics"
+ "-lobjc"
)
# Restore original suffixes
set(CMAKE_FIND_LIBRARY_SUFFIXES ${ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
+
+ # Add common library search paths for macOS (Homebrew)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/opt/homebrew/lib -L/usr/local/lib")
+ include_directories(/opt/homebrew/include /usr/local/include)
+ # SDL2_ttf headers are sometimes in SDL2/SDL_ttf.h under the include dir
+ include_directories(/opt/homebrew/include/SDL2 /usr/local/include/SDL2)
+
+ # Find and link freetype and harfbuzz if we're linking SDL2_ttf statically
+ # These are needed because static libraries don't include their dependencies
+ find_library(FREETYPE_LIB NAMES libfreetype.dylib freetype HINTS /opt/homebrew/lib /usr/local/lib)
+ find_library(HARFBUZZ_LIB NAMES libharfbuzz.dylib harfbuzz HINTS /opt/homebrew/lib /usr/local/lib)
+
+ if(FREETYPE_LIB)
+ target_link_libraries(P2ProViewer "${FREETYPE_LIB}")
+ else()
+ target_link_libraries(P2ProViewer freetype)
+ endif()
+
+ if(HARFBUZZ_LIB)
+ target_link_libraries(P2ProViewer "${HARFBUZZ_LIB}")
+ else()
+ target_link_libraries(P2ProViewer harfbuzz)
+ endif()
else()
target_link_libraries(P2ProViewer ${SDL2_LIBRARIES} ${SDL2_TTF_LIBRARIES} ${LIBUSB_LIBRARIES} ${FFMPEG_LIBRARIES})
endif()
diff --git a/MaterialIcons-Regular.ttf b/MaterialIcons-Regular.ttf
new file mode 100644
index 0000000..9d09b0f
Binary files /dev/null and b/MaterialIcons-Regular.ttf differ
diff --git a/src/AVFoundationVideoSource.mm b/src/AVFoundationVideoSource.mm
index f71b213..4b00d32 100644
--- a/src/AVFoundationVideoSource.mm
+++ b/src/AVFoundationVideoSource.mm
@@ -5,10 +5,12 @@
#include
#include
#include
+#include
@interface P2ProCaptureDelegate : NSObject {
std::vector latestFrame;
std::mutex frameMutex;
+ std::condition_variable frameCond;
BOOL hasNewFrame;
}
@property (nonatomic, strong) AVCaptureSession *session;
@@ -45,25 +47,30 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleB
// We expect YUYV (kCVPixelFormatType_422YpCbCr8) which is 2 bytes per pixel
size_t expectedBytesPerRow = width * 2;
- std::lock_guard lock(frameMutex);
- latestFrame.resize(width * height * 2);
-
- if (bytesPerRow == expectedBytesPerRow) {
- memcpy(latestFrame.data(), baseAddress, width * height * 2);
- } else {
- for (size_t y = 0; y < height; ++y) {
- memcpy(latestFrame.data() + y * expectedBytesPerRow, (uint8_t*)baseAddress + y * bytesPerRow, expectedBytesPerRow);
+ {
+ std::lock_guard lock(frameMutex);
+ latestFrame.resize(width * height * 2);
+
+ if (bytesPerRow == expectedBytesPerRow) {
+ memcpy(latestFrame.data(), baseAddress, width * height * 2);
+ } else {
+ for (size_t y = 0; y < height; ++y) {
+ memcpy(latestFrame.data() + y * expectedBytesPerRow, (uint8_t*)baseAddress + y * bytesPerRow, expectedBytesPerRow);
+ }
}
+
+ hasNewFrame = YES;
}
-
- hasNewFrame = YES;
+ frameCond.notify_all();
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
}
- (bool)getLatestFrame:(std::vector&)frameData {
- std::lock_guard lock(frameMutex);
- if (!hasNewFrame) return false;
+ std::unique_lock lock(frameMutex);
+ if (!frameCond.wait_for(lock, std::chrono::milliseconds(500), [self] { return hasNewFrame == YES; })) {
+ return false;
+ }
frameData = latestFrame;
hasNewFrame = NO;
return true;
diff --git a/src/MacOSAdapter.cpp b/src/MacOSAdapter.cpp
index a6332b3..6075fcd 100644
--- a/src/MacOSAdapter.cpp
+++ b/src/MacOSAdapter.cpp
@@ -34,7 +34,7 @@ bool MacOSAdapter::connect(uint16_t vid, uint16_t pid) {
io_iterator_t iter;
// kIOMasterPortDefault is deprecated but widely used. NULL is also acceptable.
- kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
+ kern_return_t kr = IOServiceGetMatchingServices(kIOMainPortDefault, matchingDict, &iter);
if (kr != KERN_SUCCESS) {
dprintf("MacOSAdapter::connect() - Failed to get matching services.\n");
return false;
diff --git a/src/main.cpp b/src/main.cpp
index af58b09..d50b53f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -259,8 +259,10 @@ int main(int argc, char *argv[]) {
}
P2ProFrame frame;
+ static int readFailCount = 0;
if (cameraConnected) {
if (camera.get_frame(frame)) {
+ readFailCount = 0;
hs = detectHotSpot(frame, hs.found);
tracker.update(hs, frame);
@@ -306,14 +308,18 @@ int main(int argc, char *argv[]) {
recorder.writeFrame(recordingBuffer.data());
}
} else {
- dprintf("Camera disconnected!\n");
- cameraConnected = false;
- camera.disconnect();
- if (recorder.isRecording()) {
- dprintf("Stopping recording due to disconnection.\n");
- recorder.stop();
+ readFailCount++;
+ if (readFailCount > 50) { // 50 attempts @ ~10ms = ~500ms
+ dprintf("Camera disconnected!\n");
+ cameraConnected = false;
+ camera.disconnect();
+ if (recorder.isRecording()) {
+ dprintf("Stopping recording due to disconnection.\n");
+ recorder.stop();
+ }
+ hs.found = false;
+ readFailCount = 0;
}
- hs.found = false;
}
}