Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1408ef5
Compress layout
benFears Jan 13, 2019
e1b98c2
Adjust layout
benFears Jan 13, 2019
f564cf4
Adjust layout
benFears Jan 13, 2019
f60c1fd
Adjust layout
benFears Jan 13, 2019
bb2da2a
UI Improvements
benFears Jan 20, 2019
499b83a
UI Improvements
benFears Jan 20, 2019
15a5a85
Remove Sentry
benFears Jan 20, 2019
52e234a
Add access to arenaPane
benFears Jan 20, 2019
bb89d0c
- Removed unused JavaFX HostServicesDelegate imports in `Main.java`.
jynxxNerd Oct 25, 2025
7b02900
Merge remote-tracking branch 'jynxxNerd/master'
OrBeProgrammed Mar 7, 2026
aebba09
Fix Linux support for modern distros (Pop!_OS, Ubuntu 22.04+)
OrBeProgrammed Mar 8, 2026
7484404
Fix camera capture on modern Linux with webcam-capture fallback
OrBeProgrammed Mar 8, 2026
e2e69cf
Fix webcam-capture fallback resolution and getName stability
OrBeProgrammed Mar 8, 2026
5784dd6
Fix scrambled video by converting webcam-capture images to BGR format
OrBeProgrammed Mar 8, 2026
4c20210
Replace webcam-capture fallback with ffmpeg process-based capture
OrBeProgrammed Mar 8, 2026
67186ea
Add shutdown hook for ffmpeg cleanup and redirect stderr
OrBeProgrammed Mar 8, 2026
3656d3c
Fix camera capture on Linux: use ffmpeg directly, stop discovery service
OrBeProgrammed Mar 8, 2026
02a67a9
Add detection sensitivity slider to Preferences
OrBeProgrammed Mar 8, 2026
d95c122
Detect laser streaks from CO2 recoil as valid shots
OrBeProgrammed Mar 8, 2026
69d6322
Scale camera feed to fill available window space
OrBeProgrammed Mar 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 74 additions & 60 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import groovyx.gpars.GParsPool
import groovy.xml.NamespaceBuilder
import org.kohsuke.github.GitHub
import org.ajoberstar.grgit.Grgit
import org.ajoberstar.grgit.Credentials

buildscript {
repositories {
jcenter()
mavenCentral()
maven {
url "http://repo.jenkins-ci.org/releases/"
url "https://repo.jenkins-ci.org/releases/"
}
}
dependencies {
Expand All @@ -17,10 +11,6 @@ buildscript {
}
}

plugins {
id 'org.ajoberstar.grgit' version '1.3.2'
}

apply plugin: 'java'
apply plugin: 'eclipse'

Expand Down Expand Up @@ -63,15 +53,20 @@ def keyAlias = 'cscert'
def tsaURL = 'http://timestamp.comodoca.com/rfc3161'

repositories {
jcenter()
// This repository is directly included even though we use jcenter
// because it makes it easier to get the smallest set of marytts that
// meets our needs without missing the freetts dependency
mavenCentral()
// Bintray is defunct — marytts artifacts are available on Maven Central
// and the DFKI Maven repo below. The phrack repo is no longer needed.
maven {
url "https://maven.dcm4che.org/"
}
maven {
url "http://dl.bintray.com/marytts/marytts/"
url "https://raw.githubusercontent.com/DFKI-MLT/Maven-Repository/main/"
}
maven {
url "https://dl.bintray.com/phrack/maven/"
url "https://nrgxnat.jfrog.io/artifactory/libs-release/"
}
maven {
url "https://nexus.terrestris.de/repository/public/"
}
}

Expand All @@ -80,69 +75,69 @@ configurations {

// Some dependencies (webcam-capture and marytts?) pull in slf4j implementations that conflict
// with logback
compile.exclude group: 'org.slf4j', module: 'slf4j-log4j12'
implementation.exclude group: 'org.slf4j', module: 'slf4j-log4j12'
}

dependencies {
jfxant files("$javaHome" + "/../lib/ant-javafx.jar")

// FindBugs annotations to suppress warnings
compile group: 'com.google.code.findbugs', name: 'annotations', version: '3.+'
implementation group: 'com.google.code.findbugs', name: 'annotations', version: '3.+'

// Raven, exception reporting client (requires logback)
compile group: 'com.getsentry.raven', name: 'raven-logback', version: '7.+'
//implementation group: 'com.getsentry.raven', name: 'raven-logback', version: '7.+'

// Logback to enable exception reporting
compile group: 'ch.qos.logback', name: 'logback-core', version: '1.+'
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.+'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.2.12'
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.12'

// bridj is here because webcam-capture depends on it but fetches 0.6.2, which
// does not play nicely with stackguard in newer versions of the JVM.
compile group: 'com.nativelibs4java', name: 'bridj', version: '0.7.0'
compile group: 'com.github.sarxos', name: 'webcam-capture', version: '0.3.+'
compile group: 'com.github.sarxos', name: 'webcam-capture-driver-ipcam', version: '0.3.+'
compile group: 'com.github.sarxos', name: 'webcam-capture-driver-v4l4j', version: '0.3.+'
implementation group: 'com.nativelibs4java', name: 'bridj', version: '0.7.0'
implementation group: 'com.github.sarxos', name: 'webcam-capture', version: '0.3.+'
implementation group: 'com.github.sarxos', name: 'webcam-capture-driver-ipcam', version: '0.3.+'
implementation group: 'com.github.sarxos', name: 'webcam-capture-driver-v4l4j', version: '0.3.+'

compile group: 'commons-cli', name: 'commons-cli', version: '1.+'
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.+'
compile group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.+'
implementation group: 'commons-cli', name: 'commons-cli', version: '1.+'
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.+'
implementation group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.+'

// tts dependencies
compile group: 'de.dfki.mary', name: 'marytts-runtime', version: '5.1.+'
compile group: 'de.dfki.mary', name: 'marytts-lang-en', version: '5.1.+'
compile group: 'de.dfki.mary', name: 'voice-cmu-slt-hsmm', version: '5.1.+'
implementation group: 'de.dfki.mary', name: 'marytts-runtime', version: '5.2.+'
implementation group: 'de.dfki.mary', name: 'marytts-lang-en', version: '5.2.+'
implementation group: 'de.dfki.mary', name: 'voice-cmu-slt-hsmm', version: '5.2.+'

// xuggle for recording and playing back video
compile group: 'xuggle', name: 'xuggle-xuggler', version: '5.4'
implementation group: 'xuggle', name: 'xuggle-xuggler', version: '5.4'

// JSON
compile group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1+'
implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1+'

// OpenImaj
compile('org.openimaj:core:1.+') {
implementation('org.openimaj:core:1.+') {
// OpenImaj transitive dependency that we don't need and that doesn't seem to exist in
// repos anymore
exclude group: 'vigna.dsi.unimi.it'
}

//OpenCV
compile 'org.openpnp:opencv:2.4.+'
implementation 'org.openpnp:opencv:2.4.+'

// OSHI to collect HW and system state data
compile group: 'com.github.dblock', name: 'oshi-core', version: '3.+'
implementation group: 'com.github.dblock', name: 'oshi-core', version: '3.+'

// JSoup to get processor benchmark data
compile group: 'org.jsoup', name: 'jsoup', version: '1.+'
implementation group: 'org.jsoup', name: 'jsoup', version: '1.+'

// Bluetooth libraries, QR code generator, and JSON serializer for headless mode
compile 'net.sf.bluecove:bluecove:2.1.0'
implementation 'net.sf.bluecove:bluecove:2.1.0'
// Assumption that headless mode will only be supported on Linux
compile 'net.sf.bluecove:bluecove-gpl:2.1.0'
compile 'com.google.zxing:core:3.3.0'
compile 'com.google.code.gson:gson:2.8.0'
implementation 'net.sf.bluecove:bluecove-gpl:2.1.0'
implementation 'com.google.zxing:core:3.3.0'
implementation 'com.google.code.gson:gson:2.8.0'

testCompile group: 'junit', name: 'junit', version: '4.+'
testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '1.+'
testImplementation group: 'junit', name: 'junit', version: '4.+'
testImplementation group: 'org.hamcrest', name: 'hamcrest-core', version: '1.+'
}

test {
Expand Down Expand Up @@ -177,7 +172,7 @@ task copySounds(type:Copy) {
}

task copyLibs(type:Copy) {
from { configurations.default.collect { it.isDirectory() ? it : it } }
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : it } }
into libTempDir
exclude "*jfxrt.jar"
exclude "*java2html.jar"
Expand All @@ -202,6 +197,23 @@ task updateJars() {
}
}

task simpleJar(type: Jar, dependsOn: [build, 'copyConfig', 'copyTargets', 'copyCourses', 'copySounds', 'copyLibs', 'copyEyeCam']) {
description 'Create a runnable jar for ShootOFF (without ant-javafx)'
group 'Package'

destinationDirectory = file('build/dist')
archiveFileName = 'ShootOFF.jar'

from sourceSets.main.output

manifest {
attributes(
'Main-Class': mainClassName,
'Class-Path': configurations.runtimeClasspath.collect { 'libs/' + it.name }.join(' ')
)
}
}

task fxJar(dependsOn: build) {
dependsOn('copyConfig')
dependsOn('copyTargets')
Expand All @@ -213,11 +225,11 @@ task fxJar(dependsOn: build) {
description 'Create a runnable jar for ShootOFF'
group 'Package'

inputs.dir sourceSets.main.output.classesDir
inputs.files sourceSets.main.output.classesDirs
inputs.dir sourceSets.main.output.resourcesDir
outputs.file archivePath

def antfx = NamespaceBuilder.newInstance(
def antfx = groovy.xml.NamespaceBuilder.newInstance(
ant,
'javafx:com.sun.javafx.tools.ant')

Expand All @@ -234,7 +246,9 @@ task fxJar(dependsOn: build) {

antfx.jar(destfile: archivePath) {
application(refid: project.name)
fileset(dir: sourceSets.main.output.classesDir)
sourceSets.main.output.classesDirs.each { classDir ->
fileset(dir: classDir)
}
fileset(dir: sourceSets.main.output.resourcesDir)
antfx.resources() {
fileset(dir: 'build/dist/', includes: 'libs/*.jar')
Expand Down Expand Up @@ -270,8 +284,8 @@ task zipRelease(type: Zip) {
include 'eyeCam64.dll'
}

archiveName = releaseZip
destinationDir = file(dist)
archiveFileName = releaseZip
destinationDirectory = file(dist)
}

task getDiagnosticJar() {
Expand All @@ -286,7 +300,7 @@ task getDiagnosticJar() {
password = new String(password)
}

gh = GitHub.connectUsingPassword(username, password)
gh = org.kohsuke.github.GitHub.connectUsingPassword(username, password)

if (!gh.isCredentialValid()) {
throw new InvalidUserDataException('Incorrect GitHub credentials')
Expand Down Expand Up @@ -329,7 +343,7 @@ task pushZipRelease(dependsOn: zipRelease) {
password = new String(password)
}

if (!gh) gh = GitHub.connectUsingPassword(username, password)
if (!gh) gh = org.kohsuke.github.GitHub.connectUsingPassword(username, password)

if (!gh.isCredentialValid()) {
throw new InvalidUserDataException('Incorrect GitHub credentials')
Expand Down Expand Up @@ -364,7 +378,7 @@ task pushZipRelease(dependsOn: zipRelease) {

// Update version metadata

grgit = Grgit.open('.', new Credentials(username: username, password: password))
grgit = org.ajoberstar.grgit.Grgit.open(file('.'))
grgit.checkout(branch: 'gh-pages', createBranch: false)

def versionMetadata = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n' +
Expand Down Expand Up @@ -406,7 +420,7 @@ task fxSignedJar(dependsOn: fxJar) {
}

task fxJarWritableResources() {
def antfx = NamespaceBuilder.newInstance(
def antfx = groovy.xml.NamespaceBuilder.newInstance(
ant,
'javafx:com.sun.javafx.tools.ant')

Expand All @@ -431,7 +445,7 @@ task msiRelease() {
dependsOn('updateJars')
dependsOn(getDiagnosticJar)

def antfx = NamespaceBuilder.newInstance(
def antfx = groovy.xml.NamespaceBuilder.newInstance(
ant,
'javafx:com.sun.javafx.tools.ant')

Expand Down Expand Up @@ -558,7 +572,7 @@ task fxWebstartSignedJar() {
)
}

GParsPool.withPool {
groovyx.gpars.GParsPool.withPool {
path.list().eachParallel { f ->
def antLocal = project.createAntBuilder()
antLocal.signjar(
Expand All @@ -574,7 +588,7 @@ task fxWebstartSignedJar() {
}

task fxRelease(dependsOn: fxWebstartSignedJar) {
def antfx = NamespaceBuilder.newInstance(
def antfx = groovy.xml.NamespaceBuilder.newInstance(
ant,
'javafx:com.sun.javafx.tools.ant')

Expand Down Expand Up @@ -678,7 +692,7 @@ task pushFxRelease(dependsOn: fxRelease) {
password = new String(password)
}

grgit = Grgit.open('.', new Credentials(username: username, password: password))
grgit = org.ajoberstar.grgit.Grgit.open(file('.'))
grgit.checkout(branch: 'gh-pages', createBranch: false)

delete jwsDir + 'libs'
Expand Down
37 changes: 37 additions & 0 deletions shootoff.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
# ShootOFF launcher for Linux
# Finds and preloads v4l1compat.so automatically

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Find v4l1compat.so
V4L_PATHS=(
"/usr/lib/libv4l/v4l1compat.so"
"/usr/lib/x86_64-linux-gnu/libv4l/v4l1compat.so"
"/usr/lib/aarch64-linux-gnu/libv4l/v4l1compat.so"
"/usr/lib/i386-linux-gnu/libv4l/v4l1compat.so"
)

V4L_COMPAT=""
for path in "${V4L_PATHS[@]}"; do
if [ -f "$path" ]; then
V4L_COMPAT="$path"
break
fi
done

if [ -z "$V4L_COMPAT" ]; then
# Try to find it dynamically
V4L_COMPAT=$(find /usr/lib -name "v4l1compat.so" 2>/dev/null | head -1)
fi

if [ -n "$V4L_COMPAT" ]; then
echo "Using v4l1compat: $V4L_COMPAT"
export LD_PRELOAD="$V4L_COMPAT"
else
echo "WARNING: v4l1compat.so not found. Camera may not work."
echo "Install it with: sudo apt install libv4l-0"
fi

cd "$SCRIPT_DIR"
exec java -jar build/dist/ShootOFF.jar "$@"
31 changes: 21 additions & 10 deletions src/main/java/com/shootoff/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@
import com.shootoff.util.HardwareData;
import com.shootoff.util.SystemInfo;
import com.shootoff.util.VersionChecker;
import com.sun.deploy.uitoolkit.impl.fx.HostServicesFactory;
import com.sun.javafx.application.HostServicesDelegate;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javafx.application.Application;
Expand Down Expand Up @@ -520,8 +518,7 @@ public void checkVersion() {
final Hyperlink lnk = new Hyperlink(link);

lnk.setOnAction((event) -> {
final HostServicesDelegate hostServices = HostServicesFactory.getInstance(this);
hostServices.showDocument(link);
getHostServices().showDocument(link);
lnk.setVisited(true);
});

Expand Down Expand Up @@ -549,7 +546,8 @@ public void runShootOFF() {
return;
}

if (version.isPresent() && !config.inDebugMode() && !isJWS) checkVersion();
// shootoffapp.com is defunct — skip the version check
// if (version.isPresent() && !config.inDebugMode() && !isJWS) checkVersion();

// This initializes the TTS engine
TextToSpeech.say("");
Expand Down Expand Up @@ -788,18 +786,31 @@ public static void main(String[] args) {
CameraFactory.getDefault();
} else if (SystemInfo.isLinux()) {
// Need to ensure v4l1compat is preloaded if it exists otherwise
// OpenCV won't work
final File v4lCompat = new File("/usr/lib/libv4l/v4l1compat.so");
// OpenCV won't work. Check multiple known paths since distros
// vary in where they install this library.
final String[] v4lPaths = {
"/usr/lib/libv4l/v4l1compat.so",
"/usr/lib/x86_64-linux-gnu/libv4l/v4l1compat.so",
"/usr/lib/aarch64-linux-gnu/libv4l/v4l1compat.so",
"/usr/lib/i386-linux-gnu/libv4l/v4l1compat.so"
};

File v4lCompat = null;
for (final String path : v4lPaths) {
final File candidate = new File(path);
if (candidate.exists()) {
v4lCompat = candidate;
break;
}
}

if (v4lCompat.exists()) {
if (v4lCompat != null) {
final String preload = System.getenv("LD_PRELOAD");

if (preload == null || !preload.contains(v4lCompat.getPath())) {
closeNoV4lCompat(v4lCompat);
}
} else {
// The over-exuberance here is because a lot of people miss this
// message
logger.warn("!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
+ "This system is running Linux, and likely therefore also v4l. "
+ "If ShootOFF fails to run or has camera problems, it's likely because you need "
Expand Down
Loading