diff --git a/.gitignore b/.gitignore
index fcb9baa6..7a76a506 100644
--- a/.gitignore
+++ b/.gitignore
@@ -93,3 +93,4 @@ docs/.jekyll-metadata
docs/.bundle/
flatpak/build/
.flatpak-builder/
+Elektroid.app/
diff --git a/macos-bundle.sh b/macos-bundle.sh
new file mode 100755
index 00000000..9dd37312
--- /dev/null
+++ b/macos-bundle.sh
@@ -0,0 +1,105 @@
+#!/bin/bash
+# Build a macOS .app bundle for Elektroid.
+# Requires: rsvg-convert (librsvg), Homebrew GTK dependencies.
+# The binary resolves its data directory at runtime from the bundle
+# location, so no special configure prefix is needed.
+set -euo pipefail
+
+APP_NAME="Elektroid"
+APP_BUNDLE="${APP_NAME}.app"
+CONTENTS="${APP_BUNDLE}/Contents"
+MACOS="${CONTENTS}/MacOS"
+RESOURCES="${CONTENTS}/Resources"
+SRC_DIR="$(cd "$(dirname "$0")" && pwd)"
+NPROC=$(sysctl -n hw.ncpu)
+
+cd "${SRC_DIR}"
+
+# Build if needed
+if [ ! -f src/elektroid ]; then
+ echo "==> Configuring..."
+ test -f configure || autoreconf -fi
+ ./configure 2>&1 | tail -5
+ echo "==> Building..."
+ make -j${NPROC} 2>&1 | tail -3
+fi
+
+echo "==> Creating app bundle structure..."
+rm -rf "${APP_BUNDLE}"
+mkdir -p "${MACOS}" "${RESOURCES}/share/elektroid" \
+ "${RESOURCES}/share/icons/hicolor/scalable/apps"
+
+# Copy binaries
+cp src/elektroid "${MACOS}/elektroid-bin"
+cp src/elektroid-cli "${MACOS}/"
+
+# Copy data files
+cp res/elektroid.ui res/elektroid.css res/libraries.html res/THANKS \
+ "${RESOURCES}/share/elektroid/"
+cp -R res/elektron "${RESOURCES}/share/elektroid/"
+test -d res/microbrute && cp -R res/microbrute "${RESOURCES}/share/elektroid/"
+test -d res/volca_sample_2 && cp -R res/volca_sample_2 "${RESOURCES}/share/elektroid/"
+
+# Copy icons
+cp res/*.svg "${RESOURCES}/share/icons/hicolor/scalable/apps/"
+
+# Generate .icns from SVG
+echo "==> Generating app icon..."
+ICONSET_DIR=$(mktemp -d)/Elektroid.iconset
+mkdir -p "${ICONSET_DIR}"
+SVG="res/io.github.dagargo.Elektroid.svg"
+
+for size in 16 32 64 128 256 512; do
+ rsvg-convert -w ${size} -h ${size} "${SVG}" -o "${ICONSET_DIR}/icon_${size}x${size}.png"
+done
+for size in 32 64 128 256 512 1024; do
+ half=$((size / 2))
+ rsvg-convert -w ${size} -h ${size} "${SVG}" -o "${ICONSET_DIR}/icon_${half}x${half}@2x.png"
+done
+iconutil -c icns "${ICONSET_DIR}" -o "${RESOURCES}/Elektroid.icns"
+rm -rf "$(dirname "${ICONSET_DIR}")"
+
+# Create launcher script
+cat > "${MACOS}/Elektroid" << 'LAUNCHER'
+#!/bin/bash
+DIR="$(cd "$(dirname "$0")" && pwd)"
+RESOURCES="$(cd "${DIR}/../Resources" && pwd)"
+export XDG_DATA_DIRS="${RESOURCES}/share:${XDG_DATA_DIRS:-/opt/homebrew/share:/usr/local/share:/usr/share}"
+exec "${DIR}/elektroid-bin" "$@"
+LAUNCHER
+chmod +x "${MACOS}/Elektroid"
+
+# Create Info.plist
+VERSION=$(grep 'AC_INIT' configure.ac | sed 's/.*\[\([0-9.]*\)\].*/\1/')
+cat > "${CONTENTS}/Info.plist" << PLIST
+
+
+
+
+ CFBundleName
+ ${APP_NAME}
+ CFBundleDisplayName
+ ${APP_NAME}
+ CFBundleIdentifier
+ io.github.dagargo.Elektroid
+ CFBundleVersion
+ ${VERSION}
+ CFBundleShortVersionString
+ ${VERSION}
+ CFBundleExecutable
+ Elektroid
+ CFBundleIconFile
+ Elektroid
+ CFBundlePackageType
+ APPL
+ NSHighResolutionCapable
+
+ LSMinimumSystemVersion
+ 11.0
+
+
+PLIST
+
+echo "==> Done! Created ${APP_BUNDLE}"
+echo " cp -r ${APP_BUNDLE} /Applications/"
+echo " open /Applications/${APP_BUNDLE}"
diff --git a/src/connectors/elektron.c b/src/connectors/elektron.c
index 1e17ede2..aafe2e95 100644
--- a/src/connectors/elektron.c
+++ b/src/connectors/elektron.c
@@ -4009,7 +4009,7 @@ elektron_configure_device (struct backend *backend, guint8 id)
if (err)
{
- filename = strdup (DATADIR DEVICES_FILE);
+ filename = g_build_filename (get_data_dir (), DEVICES_FILE, NULL);
err = elektron_configure_device_from_file (backend, id, filename);
g_free (filename);
}
diff --git a/src/connectors/volca_sample_2.c b/src/connectors/volca_sample_2.c
index f4c6af65..7499a6cd 100644
--- a/src/connectors/volca_sample_2.c
+++ b/src/connectors/volca_sample_2.c
@@ -832,8 +832,12 @@ volca_sample_2_pattern_clear (struct backend *backend, const gchar *path)
gint err;
struct idata init_pattern;
- err = file_load (DATADIR "/volca_sample_2/init_pattern.vlcsplpattb",
+ gchar *pattern_path = g_build_filename (get_data_dir (),
+ "volca_sample_2",
+ "init_pattern.vlcsplpattb", NULL);
+ err = file_load (pattern_path,
&init_pattern, NULL);
+ g_free (pattern_path);
if (err)
{
return err;
diff --git a/src/elektroid.c b/src/elektroid.c
index 9dc35c22..8d5d5631 100644
--- a/src/elektroid.c
+++ b/src/elektroid.c
@@ -1545,11 +1545,14 @@ elektroid_startup (GApplication *gapp, gpointer *user_data)
}
builder = gtk_builder_new ();
- gtk_builder_add_from_file (builder, DATADIR "/elektroid.ui", NULL);
+ gchar *ui_path = g_build_filename (get_data_dir (), "elektroid.ui", NULL);
+ gtk_builder_add_from_file (builder, ui_path, NULL);
+ g_free (ui_path);
css_provider = gtk_css_provider_new ();
- gtk_css_provider_load_from_path (css_provider, DATADIR "/elektroid.css",
- NULL);
+ gchar *css_path = g_build_filename (get_data_dir (), "elektroid.css", NULL);
+ gtk_css_provider_load_from_path (css_provider, css_path, NULL);
+ g_free (css_path);
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
GTK_STYLE_PROVIDER
(css_provider),
@@ -1561,11 +1564,14 @@ elektroid_startup (GApplication *gapp, gpointer *user_data)
GTK_ABOUT_DIALOG (gtk_builder_get_object (builder, "about_dialog"));
gtk_about_dialog_set_version (about_dialog, PACKAGE_VERSION);
- elektroid_about_add_credit_section (_("Libraries"),
- DATADIR "/libraries.html");
+ gchar *libraries_path = g_build_filename (get_data_dir (),
+ "libraries.html", NULL);
+ elektroid_about_add_credit_section (_("Libraries"), libraries_path);
+ g_free (libraries_path);
- elektroid_about_add_credit_section (_("Acknowledgements"),
- DATADIR "/THANKS");
+ gchar *thanks_path = g_build_filename (get_data_dir (), "THANKS", NULL);
+ elektroid_about_add_credit_section (_("Acknowledgements"), thanks_path);
+ g_free (thanks_path);
maction_context.box =
GTK_WIDGET (gtk_builder_get_object (builder, "menu_actions_box"));
diff --git a/src/mactions/microbrute.c b/src/mactions/microbrute.c
index edbdda23..033d8e00 100644
--- a/src/mactions/microbrute.c
+++ b/src/mactions/microbrute.c
@@ -259,8 +259,10 @@ void
microbrute_init ()
{
GtkBuilder *builder = gtk_builder_new ();
- gtk_builder_add_from_file (builder, DATADIR "/microbrute/microbrute.ui",
- NULL);
+ gchar *mb_ui_path = g_build_filename (get_data_dir (), "microbrute",
+ "microbrute.ui", NULL);
+ gtk_builder_add_from_file (builder, mb_ui_path, NULL);
+ g_free (mb_ui_path);
config_window = GTK_WIDGET (gtk_builder_get_object (builder,
"config_window"));
gtk_window_resize (GTK_WINDOW (config_window), 1, 1);
diff --git a/src/utils.c b/src/utils.c
index 04949d16..b9b2a940 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -25,6 +25,10 @@
#endif
#include
#include
+#ifdef __APPLE__
+#include
+#include
+#endif
#include "utils.h"
#define DEBUG_SHORT_HEX_LEN 64
@@ -188,6 +192,44 @@ get_user_dir (const char *rel_dir)
}
}
+const gchar *
+get_data_dir ()
+{
+#ifdef __APPLE__
+ static gchar *data_dir = NULL;
+ if (!data_dir)
+ {
+ char path[PATH_MAX];
+ uint32_t size = sizeof (path);
+ if (_NSGetExecutablePath (path, &size) == 0)
+ {
+ char *real = realpath (path, NULL);
+ if (real)
+ {
+ // Check for .app bundle: MacOS/bin -> Resources/share/elektroid
+ char *dir = dirname (real);
+ char *candidate = g_build_filename (dir, "..", "Resources",
+ "share", PACKAGE, NULL);
+ char *resolved = realpath (candidate, NULL);
+ g_free (candidate);
+ if (resolved)
+ {
+ data_dir = resolved;
+ }
+ free (real);
+ }
+ }
+ if (!data_dir)
+ {
+ data_dir = DATADIR;
+ }
+ }
+ return data_dir;
+#else
+ return DATADIR;
+#endif
+}
+
char *
get_system_startup_path (const gchar *local_dir)
{
diff --git a/src/utils.h b/src/utils.h
index 34b34edb..f5259510 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -139,6 +139,8 @@ gint filename_get_lenght_without_ext (const gchar * name);
gchar *get_user_dir (const gchar *);
+const gchar *get_data_dir ();
+
gchar *get_system_startup_path (const gchar *);
void free_msg (gpointer);