diff --git a/.github/workflows/build-dmg-qt6-macos.yml b/.github/workflows/build-dmg-qt6-macos.yml
new file mode 100644
index 00000000..4f04ebae
--- /dev/null
+++ b/.github/workflows/build-dmg-qt6-macos.yml
@@ -0,0 +1,149 @@
+name: Build macOS DMG (Qt6, Universal)
+
+on:
+ push:
+ tags:
+ - "*"
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: 'Checkout branch'
+ required: false
+ default: 'dev'
+ tag:
+ description: 'Checkout tag'
+ required: false
+
+permissions:
+ contents: write
+
+jobs:
+ build-omodsim:
+ name: Build OpenModSim macOS DMG version '${{ github.event.inputs.tag || github.event.inputs.branch || github.ref_name }}' with Qt6
+ runs-on: macos-latest
+
+ env:
+ QT_VERSION: "6.9.3"
+ QT_HOST: "mac"
+ QT_TARGET: "desktop"
+ QT_ARCH: "clang_64"
+ QT_INSTALL_DIR: "${{ github.workspace }}/Qt"
+ CMAKE_GENERATOR: "Ninja"
+ BUILD_TYPE: "Release"
+ DMG_PACKAGE_NAME: "qt6-omodsim"
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.inputs.tag || github.event.inputs.branch || github.ref_name }}
+
+ - name: Determine ref_type and ref_name
+ run: |
+ if [ "${{ github.ref_type }}" = "tag" ] || [ -n "${{ github.event.inputs.tag }}" ]; then
+ REF_TYPE="tags"
+ if [ -n "${{ github.event.inputs.tag }}" ]; then
+ REF_NAME="${{ github.event.inputs.tag }}"
+ else
+ REF_NAME="${{ github.ref_name }}"
+ fi
+ else
+ REF_TYPE="heads"
+ REF_NAME="${{ github.ref_name }}"
+ fi
+ echo "REF_TYPE=$REF_TYPE" >> $GITHUB_ENV
+ echo "REF_NAME=$REF_NAME" >> $GITHUB_ENV
+
+ - name: Extract version from CMakeLists.txt
+ run: |
+ FULL_VERSION=$(grep -oE 'VERSION\s+[0-9]+\.[0-9]+\.[0-9]+' src/CMakeLists.txt | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
+
+ if [ "${GITHUB_REF_NAME}" = "dev" ] || [ "${{ github.event.inputs.branch }}" = "dev" ]; then
+ MAJOR_MINOR=$(echo "$FULL_VERSION" | cut -d. -f1,2)
+ APP_VERSION="${MAJOR_MINOR}~dev"
+ else
+ APP_VERSION="$FULL_VERSION"
+ fi
+
+ echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV
+ echo "Extracted version: $APP_VERSION"
+
+ - name: Install dependencies
+ run: |
+ brew install ninja cmake
+
+ - name: Install Python (for aqtinstall)
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Install aqtinstall
+ run: python -m pip install aqtinstall
+
+ - name: Download Qt
+ run: |
+ mkdir -p ${{ env.QT_INSTALL_DIR }}
+ aqt install-qt \
+ ${{ env.QT_HOST }} \
+ ${{ env.QT_TARGET }} \
+ ${{ env.QT_VERSION }} \
+ ${{ env.QT_ARCH }} \
+ -m qt5compat qtpdf qtserialport qtserialbus \
+ -O ${{ env.QT_INSTALL_DIR }}
+
+ - name: Add Qt to PATH
+ run: echo "${{ env.QT_INSTALL_DIR }}/${{ env.QT_VERSION }}/macos/bin" >> $GITHUB_PATH
+
+ - name: Set BUILD_DIR
+ run: echo "BUILD_DIR=build-omodsim-Qt_${{ env.QT_VERSION }}_clang_64-${{ env.BUILD_TYPE }}" >> $GITHUB_ENV
+
+ - name: Configure project
+ run: |
+ cmake src -B ${{ env.BUILD_DIR }} \
+ -G "${{ env.CMAKE_GENERATOR }}" \
+ -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
+ -DCMAKE_PREFIX_PATH=${{ env.QT_INSTALL_DIR }}/${{ env.QT_VERSION }}/macos \
+ -DUSE_QT6=ON
+
+ - name: Build
+ run: cmake --build ${{ env.BUILD_DIR }} --config ${{ env.BUILD_TYPE }} --parallel
+
+ - name: Deploy Qt dependencies using macdeployqt
+ run: |
+ "${{ env.QT_INSTALL_DIR }}/${{ env.QT_VERSION }}/macos/bin/macdeployqt" \
+ "${{ env.BUILD_DIR }}/omodsim.app" \
+ -always-overwrite
+
+ - name: Copy docs into app bundle
+ run: |
+ mkdir -p "${{ env.BUILD_DIR }}/omodsim.app/Contents/Resources/docs"
+ cp -R "${{ env.BUILD_DIR }}/docs/"* "${{ env.BUILD_DIR }}/omodsim.app/Contents/Resources/docs/"
+
+ - name: Create DMG
+ run: |
+ DMG_NAME="${{ env.DMG_PACKAGE_NAME }}_${{ env.APP_VERSION }}_macos.dmg"
+ echo "DMG_NAME=$DMG_NAME" >> $GITHUB_ENV
+
+ hdiutil create -volname "Open ModSim ${{ env.APP_VERSION }}" \
+ -srcfolder "${{ env.BUILD_DIR }}/omodsim.app" \
+ -ov -format UDZO \
+ "$DMG_NAME"
+
+ - name: Upload DMG
+ uses: actions/upload-artifact@v4
+ if: success()
+ with:
+ name: ${{ env.DMG_NAME }}
+ path: ${{ env.DMG_NAME }}
+
+ - name: Create or update GitHub Release and upload DMG
+ if: success() && github.event_name == 'push' && github.ref_type == 'tag'
+ uses: softprops/action-gh-release@v2
+ with:
+ draft: true
+ tag_name: ${{ github.ref_name }}
+ name: Open ModSim ${{ env.APP_VERSION }}
+ files: |
+ ${{ env.DMG_NAME }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index cfd0228b..649df8e5 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@ Script.onInit(init);
```
# Building
- Building is available via cmake (with installed Qt version 5.15 and above) or Qt Creator. Supports both OS Microsoft Windows and Linux.
+ Building is available via cmake (with installed Qt version 5.15 and above) or Qt Creator. Supports Microsoft Windows, Linux and macOS.
## Microsoft Windows Building
@@ -176,10 +176,41 @@ cd OpenModSim
If you need to specify Qt framework major version (5 or 6), you can do it in the parameters
- `./build.sh -qt5` or `./build.sh -qt6`
+## macOS Building
+
+The minimum supported version of macOS for building OpenModSim from sources is macOS 11 (Big Sur).
+
+1. Install [Homebrew](https://brew.sh) if not already installed
+2. Install required dependencies:
+```bash
+brew install qt@6 cmake ninja
+```
+3. Clone OpenModSim sources from github repository:
+```bash
+git clone https://github.com/sanny32/OpenModSim.git
+```
+4. Go to OpenModSim folder:
+```bash
+cd OpenModSim
+```
+5. Run the build script:
+```bash
+./build-macos.sh
+```
+
+If you need to specify Qt framework major version (5 or 6), you can do it in the parameters
+ - `./build-macos.sh -qt5` or `./build-macos.sh -qt6`
+
+The build script generates a macOS application bundle (`omodsim.app`). To run the application:
+```bash
+open build-omodsim-Qt_*/omodsim.app
+```
+
# About supported operating systems
The following minimum operating system versions are supported for OpenModSim:
+-
**macOS 11 (Big Sur)** and later (Intel and Apple Silicon)
-
**Microsoft Windows 7**
-
**Debian Linux 11**
-
**Ubuntu Linux 22.04**
diff --git a/build-macos.sh b/build-macos.sh
new file mode 100755
index 00000000..6064e6f0
--- /dev/null
+++ b/build-macos.sh
@@ -0,0 +1,165 @@
+#!/usr/bin/env bash
+set -e
+
+echo "=================================="
+echo " OpenModSim build script (macOS) "
+echo "=================================="
+echo ""
+
+# ==========================
+# Check macOS
+# ==========================
+if [[ "$(uname)" != "Darwin" ]]; then
+ echo "Error: This script is for macOS only."
+ exit 1
+fi
+
+# ==========================
+# Parse script arguments
+# ==========================
+QT_CHOICE=""
+for arg in "$@"; do
+ case "$arg" in
+ -qt5|qt5)
+ QT_CHOICE="qt5"
+ ;;
+ -qt6|qt6)
+ QT_CHOICE="qt6"
+ ;;
+ *)
+ ;;
+ esac
+done
+
+if [ -z "$QT_CHOICE" ]; then
+ QT_CHOICE="qt6"
+fi
+
+# ==========================
+# Check Xcode Command Line Tools
+# ==========================
+echo "Checking for Xcode Command Line Tools..."
+if ! xcode-select -p >/dev/null 2>&1; then
+ echo "Error: Xcode Command Line Tools not found."
+ echo "Install with: xcode-select --install"
+ exit 1
+fi
+echo " Found: $(xcode-select -p)"
+
+# ==========================
+# Check Homebrew
+# ==========================
+echo "Checking for Homebrew..."
+if ! command -v brew >/dev/null 2>&1; then
+ echo "Error: Homebrew not found."
+ echo "Install from: https://brew.sh"
+ exit 1
+fi
+echo " Found: $(brew --prefix)"
+
+# ==========================
+# Check CMake and Ninja
+# ==========================
+echo "Checking for CMake..."
+if ! command -v cmake >/dev/null 2>&1; then
+ echo "CMake not found. Installing..."
+ brew install cmake
+fi
+echo " Found: $(cmake --version | head -1)"
+
+echo "Checking for Ninja..."
+if ! command -v ninja >/dev/null 2>&1; then
+ echo "Ninja not found. Installing..."
+ brew install ninja
+fi
+echo " Found: ninja $(ninja --version)"
+
+# ==========================
+# Check Qt
+# ==========================
+echo "Checking for Qt..."
+
+QT_PREFIX=""
+QT_VERSION=""
+
+if [ "$QT_CHOICE" = "qt6" ]; then
+ QT_PREFIX="$(brew --prefix qt@6 2>/dev/null || brew --prefix qt 2>/dev/null || true)"
+ if [ -z "$QT_PREFIX" ] || [ ! -d "$QT_PREFIX" ]; then
+ echo "Qt6 not found. Installing..."
+ brew install qt@6
+ QT_PREFIX="$(brew --prefix qt@6 2>/dev/null || brew --prefix qt 2>/dev/null)"
+ fi
+ QT_VERSION=$("${QT_PREFIX}/bin/qmake" -query QT_VERSION 2>/dev/null || true)
+elif [ "$QT_CHOICE" = "qt5" ]; then
+ QT_PREFIX="$(brew --prefix qt@5 2>/dev/null || true)"
+ if [ -z "$QT_PREFIX" ] || [ ! -d "$QT_PREFIX" ]; then
+ echo "Qt5 not found. Installing..."
+ brew install qt@5
+ QT_PREFIX="$(brew --prefix qt@5 2>/dev/null)"
+ fi
+ QT_VERSION=$("${QT_PREFIX}/bin/qmake" -query QT_VERSION 2>/dev/null || true)
+fi
+
+if [ -z "$QT_VERSION" ]; then
+ echo "Error: Cannot detect Qt version from ${QT_PREFIX}"
+ exit 1
+fi
+
+echo " Found: Qt ${QT_VERSION} at ${QT_PREFIX}"
+
+# ==========================
+# Check minimum Qt version
+# ==========================
+MIN_QT_VERSION="5.15.0"
+verlte() {
+ [ "$1" = "$(printf '%s\n%s' "$1" "$2" | sort -V | head -n1)" ]
+}
+verlt() {
+ [ "$1" != "$2" ] && verlte "$1" "$2"
+}
+
+if verlt "$QT_VERSION" "$MIN_QT_VERSION"; then
+ echo "Error: Qt >= $MIN_QT_VERSION is required, but found $QT_VERSION"
+ exit 1
+fi
+
+# ==========================
+# Setup cmake options
+# ==========================
+CMAKE_QT_OPTION="-DUSE_QT5=OFF -DUSE_QT6=OFF"
+if [ "$QT_CHOICE" = "qt5" ]; then
+ CMAKE_QT_OPTION="-DUSE_QT5=ON"
+elif [ "$QT_CHOICE" = "qt6" ]; then
+ CMAKE_QT_OPTION="-DUSE_QT6=ON"
+fi
+
+# ==========================
+# Detect architecture
+# ==========================
+ARCH=$(uname -m)
+BUILD_TYPE=Release
+
+# ==========================
+# Build project
+# ==========================
+SANITIZED_QT_VERSION=$(echo "$QT_VERSION" | tr '.' '_')
+BUILD_DIR="build-omodsim-Qt_${SANITIZED_QT_VERSION}_clang_${ARCH}-${BUILD_TYPE}"
+echo ""
+echo "Starting build in: ${BUILD_DIR}"
+mkdir -p "$BUILD_DIR"
+cd "$BUILD_DIR"
+
+cmake ../src -GNinja \
+ -DCMAKE_PREFIX_PATH="${QT_PREFIX}" \
+ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
+ ${CMAKE_QT_OPTION}
+
+ninja
+
+echo ""
+echo "Build finished successfully!"
+echo "Application bundle: ${BUILD_DIR}/omodsim.app"
+echo ""
+echo "To run:"
+echo " open ${BUILD_DIR}/omodsim.app"
+echo ""
diff --git a/docs/icons/logo_apple.svg b/docs/icons/logo_apple.svg
new file mode 100644
index 00000000..6a01cb14
--- /dev/null
+++ b/docs/icons/logo_apple.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 9aace2f2..1df07fbf 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -338,6 +338,15 @@ file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
find_program(QHELP_GENERATOR_EXECUTABLE qhelpgenerator
HINTS "${QT_BINARY_DIR}" "${QT_LIBEXEC_DIR}" "${QT_INSTALL_LIBEXECS}")
+if(NOT QHELP_GENERATOR_EXECUTABLE)
+ # Homebrew on macOS installs qhelpgenerator in share/qt/libexec
+ get_filename_component(_qt_share_libexec "${QT_BINARY_DIR}/../share/qt/libexec" REALPATH)
+ find_program(QHELP_GENERATOR_EXECUTABLE qhelpgenerator
+ HINTS "${_qt_share_libexec}"
+ "${CMAKE_PREFIX_PATH}/share/qt/libexec"
+ "${CMAKE_PREFIX_PATH}/libexec")
+endif()
+
if(NOT QHELP_GENERATOR_EXECUTABLE)
find_program(QHELP_GENERATOR_EXECUTABLE qhelpgenerator)
endif()
@@ -386,6 +395,25 @@ if(WIN32)
target_sources(${PROJECT_NAME} PRIVATE ${ICON_PATH} "${CMAKE_BINARY_DIR}/omodsim.rc")
set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE ON)
+elseif(APPLE)
+ set(MACOSX_ICON "${CMAKE_CURRENT_SOURCE_DIR}/res/omodsim.icns")
+ set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
+ target_sources(${PROJECT_NAME} PRIVATE ${MACOSX_ICON})
+
+ configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/omodsim.plist.in"
+ "${CMAKE_BINARY_DIR}/Info.plist"
+ @ONLY
+ )
+
+ set_target_properties(${PROJECT_NAME} PROPERTIES
+ MACOSX_BUNDLE ON
+ MACOSX_BUNDLE_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist"
+ MACOSX_BUNDLE_ICON_FILE omodsim.icns
+ MACOSX_BUNDLE_BUNDLE_NAME "${PRODUCT_NAME}"
+ MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_VERSION}"
+ MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}"
+ )
elseif(LINUX)
target_link_options(${PROJECT_NAME} PRIVATE -static-libgcc -static-libstdc++)
endif()
@@ -405,7 +433,9 @@ add_custom_command(
add_custom_target(helpgenerator ALL DEPENDS ${JSHELP_QCH} ${JSHELP_QHC})
add_dependencies(${PROJECT_NAME} helpgenerator)
-if(LINUX)
+if(APPLE)
+ install(TARGETS ${PROJECT_NAME} BUNDLE DESTINATION .)
+elseif(LINUX)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/omodsim.desktop.in"
"${CMAKE_CURRENT_BINARY_DIR}/omodsim.desktop"
diff --git a/src/dialogs/dialogabout.cpp b/src/dialogs/dialogabout.cpp
index e01ad0e5..bbef64d3 100644
--- a/src/dialogs/dialogabout.cpp
+++ b/src/dialogs/dialogabout.cpp
@@ -109,6 +109,13 @@ DialogAbout::DialogAbout(QWidget *parent) :
tr("Underlying platform."));
#endif
+ #ifdef Q_OS_MAC
+ addComponent(vboxLayout,
+ QSysInfo::prettyProductName(),
+ QSysInfo::currentCpuArchitecture(),
+ tr("Underlying platform."));
+ #endif
+
vboxLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
ui->scrollAreaComponentsWidget->setLayout(vboxLayout);
}
diff --git a/src/omodsim.plist.in b/src/omodsim.plist.in
new file mode 100644
index 00000000..17dfd1b0
--- /dev/null
+++ b/src/omodsim.plist.in
@@ -0,0 +1,28 @@
+
+
+
+
+ CFBundleExecutable
+ @PROJECT_NAME@
+ CFBundleIconFile
+ omodsim.icns
+ CFBundleIdentifier
+ org.ananev.omodsim
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ @PRODUCT_NAME@
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ @PROJECT_VERSION@
+ CFBundleVersion
+ @VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@
+ NSHighResolutionCapable
+
+ NSHumanReadableCopyright
+ Copyright 2023-@CURRENT_YEAR@ Alexandr Ananev. All rights reserved.
+ LSMinimumSystemVersion
+ 11.0
+
+
diff --git a/src/res/omodsim.icns b/src/res/omodsim.icns
new file mode 100644
index 00000000..3154ca11
Binary files /dev/null and b/src/res/omodsim.icns differ