Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
39 changes: 9 additions & 30 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ jobs:
build-appimage:
runs-on: ubuntu-24.04
needs: build-linux
permissions:
contents: read
steps:
- uses: actions/checkout@v6

Expand Down Expand Up @@ -664,16 +666,7 @@ jobs:
run: |
VERSION="${{ steps.ver.outputs.version }}"
tar -xzf "stack_wallet-linux-x86_64-${VERSION}.tar.gz"
mkdir -p AppDir
cp -r bundle/* AppDir/
cp appimage/stack_wallet/AppRun AppDir/AppRun
chmod +x AppDir/AppRun
cp appimage/stack_wallet/stack_wallet.desktop AppDir/
cp asset_sources/icon/stack_wallet/icon.png AppDir/stack_wallet.png
curl -fsSL -o appimagetool \
https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir \
appimage/build_appimage.sh stack_wallet bundle \
"stack_wallet-appimage-x86_64-${VERSION}.AppImage"

- uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -1092,6 +1085,8 @@ jobs:
build-campfire-appimage:
runs-on: ubuntu-24.04
needs: build-campfire-linux
permissions:
contents: read
steps:
- uses: actions/checkout@v6

Expand Down Expand Up @@ -1122,16 +1117,7 @@ jobs:
run: |
VERSION="${{ steps.ver.outputs.version }}"
tar -xzf "campfire-linux-x86_64-${VERSION}.tar.gz"
mkdir -p AppDir
cp -r bundle/* AppDir/
cp appimage/campfire/AppRun AppDir/AppRun
chmod +x AppDir/AppRun
cp appimage/campfire/campfire.desktop AppDir/
cp asset_sources/icon/campfire/icon.png AppDir/campfire.png
curl -fsSL -o appimagetool \
https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir \
appimage/build_appimage.sh campfire bundle \
"campfire-appimage-x86_64-${VERSION}.AppImage"

- uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -1552,6 +1538,8 @@ jobs:
build-stack-duo-appimage:
runs-on: ubuntu-24.04
needs: build-stack-duo-linux
permissions:
contents: read
steps:
- uses: actions/checkout@v6

Expand Down Expand Up @@ -1582,16 +1570,7 @@ jobs:
run: |
VERSION="${{ steps.ver.outputs.version }}"
tar -xzf "stack_duo-linux-x86_64-${VERSION}.tar.gz"
mkdir -p AppDir
cp -r bundle/* AppDir/
cp appimage/stack_duo/AppRun AppDir/AppRun
chmod +x AppDir/AppRun
cp appimage/stack_duo/stack_duo.desktop AppDir/
cp asset_sources/icon/stack_duo/icon.png AppDir/stack_duo.png
curl -fsSL -o appimagetool \
https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x appimagetool
ARCH=x86_64 ./appimagetool --appimage-extract-and-run AppDir \
appimage/build_appimage.sh stack_duo bundle \
"stack_duo-appimage-x86_64-${VERSION}.AppImage"

- uses: actions/upload-artifact@v7
Expand Down
85 changes: 85 additions & 0 deletions appimage/build_appimage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# Build an x86_64 AppImage with pinned, hash-verified AppImage tooling.
# Usage: appimage/build_appimage.sh <stack_wallet|stack_duo|campfire> <bundle dir> <output.AppImage>
set -euo pipefail

readonly APPIMAGETOOL_VERSION="1.9.1"
readonly APPIMAGETOOL_SHA256="ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0"
readonly APPIMAGE_RUNTIME_VERSION="20251108"
readonly APPIMAGE_RUNTIME_SHA256="2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d"
APPIMAGE_WORKDIR=""

verify_sha256() {
local file=$1
local expected=$2
local actual
actual=$(sha256sum "$file" | cut -d ' ' -f1)
if [ "$actual" != "$expected" ]; then
echo "SHA-256 mismatch for $file: expected $expected, got $actual" >&2
return 1
fi
}

download_verified() {
local url=$1
local expected=$2
local destination=$3
curl --fail --silent --show-error --location --retry 3 \
--connect-timeout 30 --max-time 300 \
--proto '=https' --proto-redir '=https' \
--output "$destination" "$url"
verify_sha256 "$destination" "$expected"
}

main() {
if [ $# -ne 3 ]; then
echo "usage: $0 <stack_wallet|stack_duo|campfire> <bundle dir> <output.AppImage>" >&2
return 1
fi

local app=$1
local bundle=$2
local output=$3
case "$app" in
stack_wallet|stack_duo|campfire) ;;
*) echo "unknown app: $app" >&2; return 1 ;;
esac
if [ ! -d "$bundle" ]; then
echo "bundle directory not found: $bundle" >&2
return 1
fi

local root
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
local output_dir
output_dir=$(dirname -- "$output")
mkdir -p "$output_dir"
output_dir=$(cd -- "$output_dir" && pwd)
output="$output_dir/$(basename -- "$output")"

local build_root="$root/build/appimage-work"
mkdir -p "$build_root"
APPIMAGE_WORKDIR=$(mktemp -d "$build_root/run.XXXXXX")
trap 'rm -rf -- "$APPIMAGE_WORKDIR"' EXIT

local appdir="$APPIMAGE_WORKDIR/AppDir"
local appimagetool="$APPIMAGE_WORKDIR/appimagetool-x86_64.AppImage"
local runtime="$APPIMAGE_WORKDIR/runtime-x86_64"

"$root/appimage/make_appdir.sh" "$app" "$bundle" "$appdir"
download_verified \
"https://github.com/AppImage/appimagetool/releases/download/${APPIMAGETOOL_VERSION}/appimagetool-x86_64.AppImage" \
"$APPIMAGETOOL_SHA256" "$appimagetool"
download_verified \
"https://github.com/AppImage/type2-runtime/releases/download/${APPIMAGE_RUNTIME_VERSION}/runtime-x86_64" \
"$APPIMAGE_RUNTIME_SHA256" "$runtime"
chmod +x "$appimagetool"

ARCH=x86_64 "$appimagetool" --appimage-extract-and-run \
--no-appstream --runtime-file "$runtime" \
"$appdir" "$output"
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Name=Campfire
Comment=Your privacy. Your wallet. Your Firo.
Exec=campfire
Icon=campfire
Icon=com.cypherstack.campfire
StartupWMClass=com.cypherstack.campfire
Type=Application
Categories=Finance;
50 changes: 50 additions & 0 deletions appimage/make_appdir.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Assemble an AppDir from a built Linux bundle.
# Usage: appimage/make_appdir.sh <stack_wallet|stack_duo|campfire> <bundle dir> <AppDir>
set -euo pipefail

if [ $# -ne 3 ]; then
echo "usage: $0 <stack_wallet|stack_duo|campfire> <bundle dir> <AppDir>" >&2
exit 1
fi

app=$1
bundle=$2
appdir=$3

case "$app" in
stack_wallet) id=com.cypherstack.stackwallet ;;
stack_duo) id=com.cypherstack.stackduo ;;
campfire) id=com.cypherstack.campfire ;;
*) echo "unknown app: $app" >&2; exit 1 ;;
esac

root=$(cd "$(dirname "$0")/.." && pwd)
icon="$root/asset_sources/icon/$app/icon.png"

# hicolor directories are named after the icon size, so read it from the
# PNG header (width and height are big-endian at bytes 16 and 20).
width=$((16#$(od -An -j16 -N4 -tx1 "$icon" | tr -d ' \n')))
height=$((16#$(od -An -j20 -N4 -tx1 "$icon" | tr -d ' \n')))
if [ "$width" -ne "$height" ]; then
echo "$icon is ${width}x${height}, expected a square icon" >&2
exit 1
fi

mkdir -p "$appdir"
cp -r "$bundle"/. "$appdir"/
install -m755 "$root/appimage/$app/AppRun" "$appdir/AppRun"
install -m644 "$root/appimage/$app/$id.desktop" "$appdir/$id.desktop"

# appimagetool embeds the icon named by Icon= from the AppDir root. File
# managers and integrators (Gear Lever, AppImageLauncher) read .DirIcon.
install -m644 "$icon" "$appdir/$id.png"
ln -sfn "$id.png" "$appdir/.DirIcon"

# Standard AppDir locations, also read by integrators.
install -Dm644 "$icon" \
"$appdir/usr/share/icons/hicolor/${width}x${width}/apps/$id.png"
install -Dm644 "$root/appimage/$app/$id.desktop" \
"$appdir/usr/share/applications/$id.desktop"
# No usr/share/metainfo: appimagetool would validate it with appstreamcli,
# which fetches every <url> and fails the build when one is unreachable.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Name=Stack Duo
Comment=An open-source, multicoin wallet for everyone
Exec=stack_duo
Icon=stack_duo
Icon=com.cypherstack.stackduo
StartupWMClass=com.cypherstack.stackduo
Type=Application
Categories=Finance;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Name=Stack Wallet
Comment=Open-source non-custodial cryptocurrency wallet
Exec=stack_wallet
Icon=stack_wallet
Icon=com.cypherstack.stackwallet
StartupWMClass=com.cypherstack.stackwallet
Type=Application
Categories=Finance;
1 change: 1 addition & 0 deletions flatpak/com.cypherstack.campfire.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
</description>
<url type="homepage">https://campfireprivacy.com</url>
<url type="bugtracker">https://github.com/cypherstack/stack_wallet/issues</url>
<launchable type="desktop-id">com.cypherstack.campfire.desktop</launchable>
<content_rating type="oars-1.1"/>
</component>
1 change: 1 addition & 0 deletions flatpak/com.cypherstack.stackduo.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
</description>
<url type="homepage">https://stackwallet.com</url>
<url type="bugtracker">https://github.com/cypherstack/stack_wallet/issues</url>
<launchable type="desktop-id">com.cypherstack.stackduo.desktop</launchable>
<content_rating type="oars-1.1"/>
</component>
1 change: 1 addition & 0 deletions flatpak/com.cypherstack.stackwallet.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
</description>
<url type="homepage">https://stackwallet.com</url>
<url type="bugtracker">https://github.com/cypherstack/stack_wallet/issues</url>
<launchable type="desktop-id">com.cypherstack.stackwallet.desktop</launchable>
<content_rating type="oars-1.1"/>
</component>
5 changes: 5 additions & 0 deletions scripts/app_config/templates/linux/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)

# X11 window icon, loaded by my_application.cc.
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../asset_sources/icon/${BINARY_NAME}/icon.png"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" RENAME "app_icon.png"
COMPONENT Runtime)

if(USE_SYSTEM_SECURE_STORAGE_DEPS)
file(GLOB JSONCPP_SO_FILES "${JSONCPP_LIBDIR}/libjsoncpp.so*")
install(FILES ${JSONCPP_SO_FILES} DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
Expand Down
33 changes: 33 additions & 0 deletions scripts/app_config/templates/linux/my_application.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,38 @@ static void first_frame_cb(MyApplication* self, FlView *view)
gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view)));
}

// X11 window icon from the PNG that CMake installs into data/. Wayland
// ignores this and takes the icon from the .desktop file matching
// APPLICATION_ID. GTK drops icon lists over 262144 longs, and a single
// 512x512 image is already over, so pass scaled copies.
static void set_window_icon(GtkWindow* window) {
g_autofree gchar* exe = g_file_read_link("/proc/self/exe", nullptr);
if (exe == nullptr) {
return;
}
g_autofree gchar* dir = g_path_get_dirname(exe);
g_autofree gchar* path =
g_build_filename(dir, "data", "app_icon.png", nullptr);
g_autoptr(GError) error = nullptr;
g_autoptr(GdkPixbuf) source = gdk_pixbuf_new_from_file(path, &error);
if (source == nullptr) {
g_debug("No window icon: %s", error->message);
return;
}

static const int sizes[] = {16, 24, 32, 48, 64, 128, 256};
GList* icons = nullptr;
for (int size : sizes) {
GdkPixbuf* icon =
gdk_pixbuf_scale_simple(source, size, size, GDK_INTERP_BILINEAR);
if (icon != nullptr) {
icons = g_list_append(icons, icon);
}
}
gtk_window_set_icon_list(window, icons);
g_list_free_full(icons, g_object_unref);
}

// Implements GApplication::activate.
static void my_application_activate(GApplication* application) {
MyApplication* self = MY_APPLICATION(application);
Expand All @@ -37,6 +69,7 @@ static void my_application_activate(GApplication* application) {
gtk_window_set_title(window, "PlaceHolderName");
}

set_window_icon(window);
gtk_window_set_default_size(window, 1220, 500);
gtk_widget_show(GTK_WIDGET(window));

Expand Down
Loading
Loading