Add scripts to create macOS app bundle and LaunchAgent #1244

This change introduces two new helper scripts for macOS users to improve integration with the operating system.

The `create-macos-app.sh` script builds a minimal `.app` wrapper for Solaar. This allows Solaar to be treated as a standard macOS application, enabling it to request necessary permissions, such as input monitoring, and to be discoverable by the OS. The script generates the required directory structure, a wrapper executable to launch Solaar, a standard `Info.plist` file, and an application icon from the source PNG.

The `create-macos-launchagent.sh` script sets up a LaunchAgent to automatically start Solaar at login and keep it running in the background. This ensures that the Solaar process is always available for device management without requiring manual intervention from the user. The script also configures log file locations for standard output and error streams.

Together, these scripts provide a more native and user-friendly experience for Solaar on macOS.

https://github.com/pwr-Solaar/Solaar/issues/1244
This commit is contained in:
Din Tort 2025-10-27 00:25:10 +01:00
parent 5c94cf4d9f
commit 8fd14fefa1
2 changed files with 161 additions and 0 deletions

95
tools/create-macos-app.sh Executable file
View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
# Helper to build a minimal macOS .app wrapper for Solaar.
set -euo pipefail
APP_ROOT=${1:-/Applications/Solaar.app}
SOLAR_PATH=${SOLAR_PATH:-/opt/homebrew/bin/solaar}
ICON_SOURCE=${ICON_SOURCE:-share/solaar/icons/solaar-light_100.png}
if [[ ! -x "${SOLAR_PATH}" ]]; then
echo "Error: Unable to execute ${SOLAR_PATH}. Set SOLAR_PATH to the solaar binary." >&2
exit 1
fi
case "${APP_ROOT}" in
""|"/"|".")
echo "Error: Refusing to create app bundle at unsafe location: \"${APP_ROOT}\"" >&2
exit 1
;;
esac
echo "Creating Solaar app bundle at ${APP_ROOT}"
rm -rf "${APP_ROOT}"
APP_CONTENTS="${APP_ROOT}/Contents"
MACOS_DIR="${APP_CONTENTS}/MacOS"
RESOURCES_DIR="${APP_CONTENTS}/Resources"
mkdir -p "${MACOS_DIR}" "${RESOURCES_DIR}"
WRAPPER="${MACOS_DIR}/solaar-wrapper"
cat > "${WRAPPER}" <<EOF
#!/usr/bin/env bash
set -euo pipefail
exec "${SOLAR_PATH}" --window=hide "\$@"
EOF
chmod +x "${WRAPPER}"
HAVE_ICON=0
if command -v sips >/dev/null 2>&1 && command -v iconutil >/dev/null 2>&1 && [[ -f "${ICON_SOURCE}" ]]; then
TMP_ICONSET=$(mktemp -d /tmp/solaar-iconset.XXXXXX)
trap 'rm -rf "${TMP_ICONSET}"' EXIT
for SIZE in 16 32 64 128 256 512; do
sips -s format png -z "${SIZE}" "${SIZE}" "${ICON_SOURCE}" --out "${TMP_ICONSET}/icon_${SIZE}x${SIZE}.png" >/dev/null
DOUBLE=$((SIZE * 2))
sips -s format png -z "${DOUBLE}" "${DOUBLE}" "${ICON_SOURCE}" --out "${TMP_ICONSET}/icon_${SIZE}x${SIZE}@2x.png" >/dev/null
done
if iconutil -c icns "${TMP_ICONSET}" -o "${RESOURCES_DIR}/solaar.icns" >/dev/null 2>&1; then
HAVE_ICON=1
echo "Added icon from ${ICON_SOURCE}"
else
echo "Warning: Failed to create solaar.icns continuing without custom icon" >&2
fi
rm -rf "${TMP_ICONSET}"
trap - EXIT
else
echo "Skipping icon generation (requires sips, iconutil, and ${ICON_SOURCE})"
fi
{
cat <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>solaar-wrapper</string>
<key>CFBundleIdentifier</key>
<string>io.github.pwr-solaar.solaar</string>
<key>CFBundleName</key>
<string>Solaar</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>11.0</string>
<key>NSInputMonitoringUsageDescription</key>
<string>Solaar needs to access input devices to configure and monitor your Logitech keyboards, mice, and other peripherals.</string>
EOF
if [[ ${HAVE_ICON} -eq 1 ]]; then
cat <<'EOF'
<key>CFBundleIconFile</key>
<string>solaar.icns</string>
EOF
fi
cat <<'EOF'
</dict>
</plist>
EOF
} > "${APP_CONTENTS}/Info.plist"
echo "Solaar app bundle created at ${APP_ROOT}"
echo ""
echo "To install the LaunchAgent for automatic startup and keep-alive execute:"
echo " bash tools/create-macos-launchagent.sh"

View File

@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Helper to install a LaunchAgent for Solaar to keep it running in the background.
set -euo pipefail
SOLAR_PATH=${SOLAR_PATH:-/opt/homebrew/bin/solaar}
if [[ ! -x "${SOLAR_PATH}" ]]; then
echo "Error: Unable to execute ${SOLAR_PATH}. Set SOLAR_PATH to the solaar binary." >&2
exit 1
fi
LAUNCH_AGENT_DIR="${HOME}/Library/LaunchAgents"
LAUNCH_AGENT_PLIST="${LAUNCH_AGENT_DIR}/io.github.pwr-solaar.solaar.plist"
mkdir -p "${LAUNCH_AGENT_DIR}"
echo "Creating LaunchAgent to keep Solaar running..."
# Unload if already loaded (suppress errors)
launchctl unload "${LAUNCH_AGENT_PLIST}" 2>/dev/null || true
cat > "${LAUNCH_AGENT_PLIST}" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>io.github.pwr-solaar.solaar</string>
<key>ProgramArguments</key>
<array>
<string>${SOLAR_PATH}</string>
<string>--window=hide</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>${HOME}/Library/Logs/solaar.log</string>
<key>StandardErrorPath</key>
<string>${HOME}/Library/Logs/solaar.error.log</string>
<key>ProcessType</key>
<string>Background</string>
</dict>
</plist>
EOF
launchctl load "${LAUNCH_AGENT_PLIST}"
echo "LaunchAgent created at ${LAUNCH_AGENT_PLIST}"
echo ""
echo "To disable automatic startup:"
echo " launchctl unload \"${LAUNCH_AGENT_PLIST}\""
echo ""
echo "To re-enable automatic startup:"
echo " launchctl load \"${LAUNCH_AGENT_PLIST}\""
echo ""
echo "To start Solaar:"
echo " launchctl start io.github.pwr-solaar.solaar"
echo ""
echo "To stop Solaar:"
echo " launchctl stop io.github.pwr-solaar.solaar"
echo ""
echo "Logs will be written to:"
echo " ${HOME}/Library/Logs/solaar.log"
echo " ${HOME}/Library/Logs/solaar.error.log"