diff --git a/SerialPrograms/BuildInstructions/Build-Ubuntu-Qt6.8.2.md b/SerialPrograms/BuildInstructions/Build-Ubuntu-Qt6.8.2.md
index ad65c8191a..198091f76c 100644
--- a/SerialPrograms/BuildInstructions/Build-Ubuntu-Qt6.8.2.md
+++ b/SerialPrograms/BuildInstructions/Build-Ubuntu-Qt6.8.2.md
@@ -1,15 +1,16 @@
-# How to Build (Qt 6.8.2) - Ubuntu 24.04
+# How to Build (Qt 6.8.2) - Linux
-Note that our Ubuntu setup does not work. The video display flickers to the point of being unusable.
+Note that our Linux setup might not work. The video display can flicker to the point of being unusable.
## Build Tools:
-Install the following packages:
+Install the following build requirements, these steps will use ubuntu packages but the steps will be the same on your distro:
```
-sudo apt install cmake
-sudo apt install libglx-dev libgl1-mesa-dev
-sudo apt install libopencv-dev
+git cmake ninja-build gcc g++ qt6-base-dev qt6-multimedia-dev qt6-serialport-dev libopencv-dev libonnx-dev libasound2-dev libsystemd-dev
+dpkg # To build .deb packages for Debian based distros
+rpm # To build .rpm packages for RHEL based distros
+pacman-package-manager # To build .pkg packages for Arch based distros
```
@@ -30,20 +31,30 @@ sudo apt install libopencv-dev


+### Alternatively:
+1. ```curl -L https://download.qt.io/official_releases/online_installers/qt-online-installer-linux-x64-online.run -o qt.run```
+2. ```chmod +x qt.run```
+3. ```./qt.run install qt6.8.2-full-dev```
+4. ```export CMAKE_PREFIX_PATH=/opt/Qt/6.8.2/gcc_64:$CMAKE_PREFIX_PATH```
+
## Setup:
+### Package:
1. Clone this repo.
-2. Clone the [Packages Repo](https://github.com/PokemonAutomation/Packages).
-3. In the `Packages` repo, copy the `SerialPrograms/Resources` folder into the root of the `Arduino-Source` repo.
-
-
-
-4. Open Qt Creator.
-5. Click on `File` -> `Open File or Project`.
-6. Navigate to `SerialPrograms` and select `CMakeLists.txt`.
-7. Enable parallel build: Build & Run -> Build Steps -> Build -> Details -> CMake arguments: `-j16` (the # of cores you have)
-8. At the bottom left corner, click on the little monitor and select `Release with Debug Information`.
-9. Still in the bottom left corner, click the upper green arrow to compile and launch the program.
+2. Open a terminal in the folder ```Arduino-Source/SerialPrograms/Scripts/Linux```
+3. Run the script ```./packages.sh (PACKAGE TYPE)```
+ - PACKAGE TYPE:
+ - DEB - .deb package
+ - RPM - .rpm package
+ - PKG - .pkg package
+ - TGZ - Generic package
+4. Find the package at ```/Arduino-Source/SerialPrograms/build```
+5. Install the package ```apt install ./pokemon-automation--x86_64.deb```
+
+### Source:
+1. Clone this repo.
+2. Open a terminal in ```Arduino-Source/SerialPrograms```
+3. Compile the source code ```cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2 -march=native -Wno-odr" -DCMAKE_CXX_FLAGS="-O2 -march=native -Wno-odr" && ninja -C build```
diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt
index ec74150758..d0429851e8 100644
--- a/SerialPrograms/CMakeLists.txt
+++ b/SerialPrograms/CMakeLists.txt
@@ -713,3 +713,4 @@ endif()
# Add command-line executable (GUI-free) from subdirectory
include(Source/CommandLine/CommandLineExecutable.cmake)
+include(cmake/CPack.cmake)
\ No newline at end of file
diff --git a/SerialPrograms/Scripts/Linux/PKGBUILD b/SerialPrograms/Scripts/Linux/PKGBUILD
new file mode 100644
index 0000000000..c1d11569b5
--- /dev/null
+++ b/SerialPrograms/Scripts/Linux/PKGBUILD
@@ -0,0 +1,65 @@
+pkgname=pokemon-automation
+pkgver=1
+pkgrel=1
+pkgdesc="Pokemon Automation Serial Programs"
+arch=('x86_64')
+url="https://github.com/PokemonAutomation/Arduino-Source"
+license=('MIT')
+options=('!debug')
+
+depends=('qt6-base' 'qt6-multimedia' 'qt6-serialport' 'qt6-imageformats' \
+ 'qt6-multimedia-gstreamer' 'gstreamer' 'opencv' 'mesa' \
+ 'libglvnd' 'hdf5' 'vtk' 'tesseract' 'onnxruntime')
+
+makedepends=('cmake' 'ninja')
+
+source=("Arduino-Source.tar.gz")
+sha256sums=('SKIP')
+
+build() {
+ CCACHE_ARGS=()
+ if command -v ccache >/dev/null 2>&1; then
+ echo "[INFO] Using ccache for compilation"
+ CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
+ fi
+
+ mkdir -p "$srcdir/Arduino-Source/SerialPrograms/build"
+ cd "$srcdir/Arduino-Source/SerialPrograms/build"
+
+ cmake .. \
+ -G Ninja \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_C_FLAGS="-O2 -g0 -fno-record-gcc-switches -fno-ident -Wno-odr" \
+ -DCMAKE_CXX_FLAGS="-O2 -g0 -fno-record-gcc-switches -fno-ident -Wno-odr" \
+ "${CCACHE_ARGS[@]}"
+
+ ninja -j$(( $(nproc) - 1))
+}
+
+package() {
+ install -Dm755 $srcdir/Arduino-Source/SerialPrograms/build/SerialPrograms \
+ "$pkgdir/usr/bin/SerialPrograms"
+
+ install -d "$pkgdir/usr/share/SerialPrograms"
+ cp -r "$srcdir/Arduino-Source/SerialPrograms/build/Resources/." \
+ "$pkgdir/usr/share/SerialPrograms/Resources/"
+ cp -r "$srcdir/Arduino-Source/SerialPrograms/build/Firmware/." \
+ "$pkgdir/usr/share/SerialPrograms/Firmware/"
+
+ install -d "$pkgdir/usr/share/icons/hicolor/256x256/apps"
+ install -Dm644 $srcdir/Arduino-Source/IconResource/SerialPrograms.png \
+ "$pkgdir/usr/share/icons/hicolor/256x256/apps/SerialPrograms.png"
+
+ install -d "$pkgdir/usr/share/applications"
+ cat > "$pkgdir/usr/share/applications/SerialPrograms.desktop" </dev/null 2>&1; then
+ echo " [MISSING] $pkg requires $TOOL"
+ TARGETS=("${TARGETS[@]/$pkg}")
+ else
+ echo " [OK] $pkg -> $TOOL found"
+ fi
+ else
+ echo " [OK] $pkg -> no external tool required"
+ fi
+done
+
+
+# Check CCache
+CCACHE_ARGS=()
+if command -v ccache >/dev/null 2>&1; then
+ echo " [INFO] ccache found, caching compilation"
+ CCACHE_ARGS=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
+fi
+
+
+# Build CMake
+cd "$SOURCE_DIR"
+cmake "$SOURCE_DIR" -G Ninja -B build \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_C_FLAGS="-O2 -g0 -fno-record-gcc-switches -fno-ident -Wno-odr" \
+ -DCMAKE_CXX_FLAGS="-O2 -g0 -fno-record-gcc-switches -fno-ident -Wno-odr" \
+ "${CCACHE_ARGS[@]}"
+
+cd "$SOURCE_DIR/build"
+ninja -j$(( $(nproc) - 1))
+
+
+# Build Packages
+cd "$BUILD_DIR"
+for pkg in "${TARGETS[@]}"; do
+ case "$pkg" in
+ DEB|RPM|TGZ)
+ echo "Building $pkg package with CPack..."
+ cpack -G "$pkg"
+ ;;
+ PKG)
+ echo "Building PKG package with makepkg..."
+ cp "$SCRIPT_DIR/PKGBUILD" "."
+ git -C "$SOURCE_DIR/.." archive --format=tar.gz --prefix=Arduino-Source/ \
+ HEAD > "Arduino-Source.tar.gz"
+ makepkg -Cf
+ ;;
+ *)
+ echo "Unknown package type: $pkg"
+ ;;
+ esac
+done
+
+echo "Package creation complete."
+echo "Packages can be found here: $BUILD_DIR"
\ No newline at end of file
diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
index 0305055e1e..1fec25fef8 100644
--- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
+++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp
@@ -118,7 +118,7 @@ GlobalSettings::GlobalSettings()
false,
"Stats File:
Use the stats file here. Multiple instances of the program can use the same file.",
LockMode::LOCK_WHILE_RUNNING,
-#if defined(__APPLE__)
+#if defined(__APPLE__) || defined(__linux__)
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString() + "/UserSettings/PA-Stats.txt",
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString() + "/UserSettings/PA-Stats.txt"
#else
@@ -133,6 +133,10 @@ GlobalSettings::GlobalSettings()
#if defined(__APPLE__)
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString() + "/TempFiles/",
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString() + "/TempFiles/"
+#elif defined(__linux__)
+ // /tmp/
+ QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString(),
+ QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString()
#else
"TempFiles/",
"TempFiles/"
diff --git a/SerialPrograms/Source/CommonFramework/Globals.cpp b/SerialPrograms/Source/CommonFramework/Globals.cpp
index 192a54f045..6d2d95b4de 100644
--- a/SerialPrograms/Source/CommonFramework/Globals.cpp
+++ b/SerialPrograms/Source/CommonFramework/Globals.cpp
@@ -114,6 +114,10 @@ const size_t LOG_HISTORY_LINES = 10000;
namespace{
QString get_application_base_dir_path(){
+#if defined(__linux__)
+ // ~/.local/share/SerialPrograms
+ return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+#else
QString application_dir_path = qApp->applicationDirPath();
if (application_dir_path.endsWith(".app/Contents/MacOS")){
// a macOS bundle. Change working directory to the folder that hosts the .app folder.
@@ -122,8 +126,24 @@ QString get_application_base_dir_path(){
return base_folder_path;
}
return application_dir_path;
+#endif
}
std::string get_resource_path(){
+#if defined(__linux__)
+ // Look for Resources installed as part of a package
+ QString system_path = "/usr/share/SerialPrograms/Resources/";
+ if (QDir(system_path).exists()) {
+ return system_path.toStdString();
+ }
+ // Check for Resources in the current working directory
+ QString cwd_path = "./Resources/";
+ if (QDir(cwd_path).exists()) {
+ return cwd_path.toStdString();
+ }
+ // Prevents a prompt telling the user to install Resources to /usr/Resources
+ // if the binary is located at /usr/bin/SerialPrograms
+ return (get_application_base_dir_path() + "/Resources/").toStdString();
+#else
// Find the resource directory.
QString path = get_application_base_dir_path();
for (size_t c = 0; c < 5; c++){
@@ -135,6 +155,7 @@ std::string get_resource_path(){
path += "/..";
}
return (QCoreApplication::applicationDirPath() + "/../Resources/").toStdString();
+#endif
}
std::string get_training_path(){
// Find the training data directory.
@@ -151,6 +172,10 @@ std::string get_training_path(){
}
std::string get_runtime_base_path(){
+#if defined(__linux__)
+ // ~/.local/share/SerialPrograms/
+ return (get_application_base_dir_path() + "/").toStdString();
+#else
// On MacOS, find the writable application support directory
if (QSysInfo::productType() == "macos" || QSysInfo::productType() == "osx"){
// QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) returns
@@ -167,6 +192,7 @@ std::string get_runtime_base_path(){
return appSupportPath.toStdString() + "/";
}
return "./";
+#endif
}
std::string get_setting_path(){
diff --git a/SerialPrograms/Source/CommonFramework/Main.cpp b/SerialPrograms/Source/CommonFramework/Main.cpp
index 455b9a2f96..d231c2080d 100644
--- a/SerialPrograms/Source/CommonFramework/Main.cpp
+++ b/SerialPrograms/Source/CommonFramework/Main.cpp
@@ -39,6 +39,7 @@
#include "Windows/MainWindow.h"
#include
+#include
using std::cout;
using std::endl;
@@ -48,6 +49,12 @@ using namespace PokemonAutomation;
Q_DECLARE_METATYPE(std::string)
void set_working_directory(){
+#if defined(__linux__)
+ // ~/.local/share/SerialPrograms
+ QString base_folder_path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+ std::cout << base_folder_path.toStdString() << std::endl;
+ QDir::setCurrent(base_folder_path);
+#else
QString application_dir_path = qApp->applicationDirPath();
if (application_dir_path.endsWith(".app/Contents/MacOS")){
// a macOS bundle. Change working directory to the folder that hosts the .app folder.
@@ -55,6 +62,7 @@ void set_working_directory(){
QString base_folder_path = QFileInfo(app_bundle_path).dir().absolutePath();
QDir::setCurrent(base_folder_path);
}
+#endif
}
diff --git a/SerialPrograms/cmake/CPack.cmake b/SerialPrograms/cmake/CPack.cmake
new file mode 100644
index 0000000000..daa6063809
--- /dev/null
+++ b/SerialPrograms/cmake/CPack.cmake
@@ -0,0 +1,78 @@
+### Packaging
+# Install binary
+install(
+ TARGETS SerialPrograms
+ RUNTIME DESTINATION bin
+)
+
+# Resources
+install(
+ DIRECTORY ${CMAKE_BINARY_DIR}/Resources/
+ DESTINATION share/SerialPrograms/Resources
+)
+
+# Firmware
+install(
+ DIRECTORY ${CMAKE_BINARY_DIR}/Firmware/
+ DESTINATION share/SerialPrograms/Firmware
+)
+
+# App icon
+install(
+ FILES ${CMAKE_SOURCE_DIR}/../IconResource/SerialPrograms.png
+ DESTINATION share/icons/hicolor/256x256/apps
+)
+
+# Desktop Entry
+install(
+ FILES ${CMAKE_SOURCE_DIR}/../IconResource/SerialPrograms.desktop
+ DESTINATION share/applications
+)
+
+# License
+install(
+ FILES ${CMAKE_SOURCE_DIR}/../LICENSE
+ DESTINATION share/licenses/SerialPrograms
+)
+
+# Discord Partner Library
+install(
+ FILES ${CMAKE_SOURCE_DIR}/../3rdPartyBinaries/discord_social_sdk_linux/lib/release/libdiscord_partner_sdk.so
+ DESTINATION lib
+ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
+)
+
+# ONNX Runtime Library
+install(
+ FILES ${CMAKE_BINARY_DIR}/onnxruntime-linux-x64-1.23.0/lib/libonnxruntime.so.1.23.0
+ DESTINATION lib
+ RENAME libonnxruntime.so.1
+ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
+)
+
+### CPack Config
+set(CPACK_GENERATOR "DEB;RPM;TGZ")
+set(CPACK_PACKAGE_NAME "pokemon-automation")
+set(CPACK_PACKAGE_VERSION ${SerialPrograms_VERSION})
+set(CPACK_PACKAGING_INSTALL_PREFIX "/usr")
+set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${PACKAGE_VERSION}-${CMAKE_SYSTEM_PROCESSOR}")
+
+# Debian
+set(CPACK_DEBIAN_PACKAGE_MAINTAINER "PokemonAutomation")
+set(CPACK_DEBIAN_PACKAGE_DEPENDS
+ "libqt6core6, libqt6gui6, libqt6widgets6, libqt6multimedia6, libqt6serialport6, libqt6multimediawidgets6, \
+ qt6-image-formats-plugins, gstreamer1.0-plugins-base, gstreamer1.0-plugins-good, gstreamer1.0-plugins-bad, \
+ gstreamer1.0-plugins-ugly, libopencv-core410, libopencv-imgproc410, libglx0, libgl1, libhdf5-310, libvtk9.3, \
+ tesseract-ocr"
+)
+
+# RPM
+set(CPACK_RPM_PACKAGE_LICENSE "MIT")
+set(CPACK_RPM_PACKAGE_GROUP "Applications/Utilities")
+set(CPACK_RPM_PACKAGE_REQUIRES
+ "qt6-qtbase, qt6-qtmultimedia, qt6-qtserialport, qt6-qtimageformats, gstreamer1, gstreamer1-plugins-base, \
+ gstreamer1-plugins-good, gstreamer1-plugins-bad-free, gstreamer1-plugins-ugly, gstreamer-plugin-libav, \
+ opencv, mesa-libGL, libglvnd, hdf5, vtk, tesseract, leptonica, onnxruntime"
+)
+
+include(CPack)
\ No newline at end of file