mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Fry3DX 0.5: builds for Windows, Linux and Android, a phone layout, Apache 2.0
- Apache License 2.0 - Windows: cross-built with clang-cl (cmake --preset windows), a window program with its icon; Linux: a portable preset (SDL and the C++ library inside); Android: android/build_apk.py makes the APK - tools/package.py packs the downloads; a GitHub workflow builds all three and makes a release for a v* tag - A phone layout (fry_touch, on by default on Android): the UI drawn bigger, panels when asked for, a bar of big buttons, one finger to turn the view, two to move and pinch, a long press for a right-click; files through the phone's file picker - Settings and autosaves now in a Nexoniarz folder (copied over once) - README rewritten, the website shows the downloads and the phone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NMSrd5oe1GpUD7E6d825KP
This commit is contained in:
parent
f3e8eca87b
commit
f6f5e5f288
40 changed files with 1180 additions and 158 deletions
30
android/AndroidManifest.xml
Normal file
30
android/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Fry3DX on Android (android/build_apk.py): arm64, Vulkan. -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.github.nexoniarz.fry3dx"
|
||||
android:installLocation="auto">
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x403000" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
|
||||
<application
|
||||
android:label="Fry3DX"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:hasCode="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:allowBackup="true"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
|
||||
<activity
|
||||
android:name="io.github.nexoniarz.fry3dx.FryActivity"
|
||||
android:exported="true"
|
||||
android:label="Fry3DX"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:launchMode="singleInstance"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
116
android/build_apk.py
Normal file
116
android/build_apk.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Builds Fry3DX's Android APK (arm64, Vulkan) with no Gradle: the NDK build (cmake --preset
|
||||
android), the content as plain files in assets/, SDL's Java layer, the icon, then aapt2, d8,
|
||||
zipalign and apksigner.
|
||||
|
||||
nix-shell android/shell.nix --run "python3 android/build_apk.py [--install]"
|
||||
|
||||
Out: dist/Fry3DX-<version>-android-arm64.apk. --install puts it on a phone over adb (USB
|
||||
debugging on) and starts it. The first start copies the content out (a moment).
|
||||
Signed with ~/.local/share/Nexoniarz/Fry3DX-signing/release.keystore (made the first time),
|
||||
or the key in $FRY_KEYSTORE / $FRY_KEYSTORE_PASS. Keep it safe and keep using it: Android only
|
||||
updates an app signed with the key it was installed with.
|
||||
"""
|
||||
import os
|
||||
import secrets
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
BUILD = os.path.join(ROOT, "build", "android")
|
||||
APP = "Fry3DX"
|
||||
PACKAGE = "io.github.nexoniarz.fry3dx"
|
||||
|
||||
|
||||
def run(*cmd, **kw):
|
||||
subprocess.run(list(cmd), check=True, **kw)
|
||||
|
||||
|
||||
def app_version():
|
||||
with open(os.path.join(ROOT, "CMakeLists.txt")) as f:
|
||||
for line in f:
|
||||
if line.startswith("project(Fry3DX VERSION "):
|
||||
return line.split()[2]
|
||||
return "0"
|
||||
|
||||
|
||||
def main():
|
||||
sdk, ndk = os.environ["ANDROID_HOME"], os.environ["ANDROID_NDK_ROOT"]
|
||||
tools = os.path.join(sdk, "build-tools", "35.0.0")
|
||||
android_jar = os.path.join(sdk, "platforms", "android-35", "android.jar")
|
||||
llvm = os.path.join(ndk, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin")
|
||||
|
||||
if not os.path.isdir(BUILD):
|
||||
glslc = os.environ.get("GLSLC") or os.path.join(ndk, "shader-tools", "linux-x86_64", "glslc") # (the NDK's own, if none's given)
|
||||
run("cmake", "--preset", "android", *([f"-DGLSLC={glslc}"] if os.path.exists(glslc) else []), cwd=ROOT)
|
||||
run("cmake", "--build", "--preset", "android", cwd=ROOT)
|
||||
|
||||
stage = os.path.join(BUILD, "apk")
|
||||
shutil.rmtree(stage, ignore_errors=True)
|
||||
assets, libs = os.path.join(stage, "assets"), os.path.join(stage, "lib", "arm64-v8a")
|
||||
os.makedirs(libs)
|
||||
bin_dir = os.path.join(BUILD, "bin")
|
||||
sdl = os.path.join(BUILD, "_deps", "sdl3-build", "libSDL3.so")
|
||||
for src in (os.path.join(bin_dir, "libmain.so"), sdl):
|
||||
run(os.path.join(llvm, "llvm-strip"), "--strip-debug", "-o", os.path.join(libs, os.path.basename(src)), src)
|
||||
|
||||
# Content: the folders next to the program on a desktop, and the list it unpacks from (a
|
||||
# version on its first line, so an update unpacks again).
|
||||
for d in ("engine", APP):
|
||||
shutil.copytree(os.path.join(bin_dir, d), os.path.join(assets, d))
|
||||
commit = subprocess.run(["git", "rev-parse", "--short", "HEAD"], cwd=ROOT, capture_output=True, text=True).stdout.strip() or "local"
|
||||
version = f"{app_version()}-{commit}"
|
||||
files = [os.path.relpath(os.path.join(r, n), assets).replace(os.sep, "/") for r, _, ns in os.walk(assets) for n in ns]
|
||||
with open(os.path.join(assets, "content.txt"), "w") as f:
|
||||
f.write(f"{version}-{int(time.time())}\n" + "\n".join(sorted(files)) + "\n")
|
||||
|
||||
# Java: SDL's activity and helpers, and ours on top; then dex.
|
||||
java_src = [os.path.join(ROOT, "android", "java"), os.path.join(BUILD, "_deps", "sdl3-src", "android-project", "app", "src", "main", "java")]
|
||||
sources = [os.path.join(r, n) for d in java_src for r, _, ns in os.walk(d) for n in ns if n.endswith(".java")]
|
||||
classes = os.path.join(BUILD, "java-classes")
|
||||
shutil.rmtree(classes, ignore_errors=True)
|
||||
os.makedirs(classes)
|
||||
print(f"Compiling {len(sources)} Java files")
|
||||
run("javac", "-nowarn", "-source", "8", "-target", "8", "-bootclasspath", android_jar, "-d", classes, *sources,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
class_files = [os.path.join(r, n) for r, _, ns in os.walk(classes) for n in ns if n.endswith(".class")]
|
||||
run(os.path.join(tools, "d8"), "--release", "--min-api", "28", "--lib", android_jar, "--output", stage, *class_files)
|
||||
|
||||
# Resources (the icon), the manifest and the assets; then dex and libraries; aligned, signed.
|
||||
res_zip = os.path.join(BUILD, "res.zip")
|
||||
run(os.path.join(tools, "aapt2"), "compile", "--dir", os.path.join(ROOT, "android", "res"), "-o", res_zip)
|
||||
unsigned = os.path.join(BUILD, "unsigned.apk")
|
||||
vcode = str(int(time.time()) // 60 % 2000000000)
|
||||
run(os.path.join(tools, "aapt2"), "link", "-o", unsigned, "-I", android_jar, "--manifest", os.path.join(ROOT, "android", "AndroidManifest.xml"),
|
||||
"-A", assets, "-R", res_zip, "--auto-add-overlay", "--min-sdk-version", "28", "--target-sdk-version", "35",
|
||||
"--version-code", vcode, "--version-name", version)
|
||||
with zipfile.ZipFile(unsigned, "a") as z:
|
||||
z.write(os.path.join(stage, "classes.dex"), "classes.dex")
|
||||
for n in os.listdir(libs):
|
||||
z.write(os.path.join(libs, n), f"lib/arm64-v8a/{n}")
|
||||
aligned = os.path.join(BUILD, "aligned.apk")
|
||||
run(os.path.join(tools, "zipalign"), "-p", "-f", "4", unsigned, aligned)
|
||||
keystore = os.environ.get("FRY_KEYSTORE") or os.path.expanduser("~/.local/share/Nexoniarz/Fry3DX-signing/release.keystore")
|
||||
pass_file = keystore + ".pass" # (its password, beside it, when made here)
|
||||
password = os.environ.get("FRY_KEYSTORE_PASS") or (open(pass_file).read().strip() if os.path.exists(pass_file) else "")
|
||||
if not os.path.exists(keystore):
|
||||
os.makedirs(os.path.dirname(keystore), exist_ok=True)
|
||||
password = password or secrets.token_urlsafe(24)
|
||||
with open(os.open(pass_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600), "w") as f:
|
||||
f.write(password + "\n")
|
||||
run("keytool", "-genkeypair", "-keystore", keystore, "-storepass", password, "-keypass", password, "-alias", "fry3dx",
|
||||
"-keyalg", "RSA", "-keysize", "2048", "-validity", "10000", "-dname", "CN=Fry3DX", stdout=subprocess.DEVNULL)
|
||||
os.makedirs(os.path.join(ROOT, "dist"), exist_ok=True)
|
||||
out = os.path.join(ROOT, "dist", f"{APP}-{app_version()}-android-arm64.apk")
|
||||
run(os.path.join(tools, "apksigner"), "sign", "--ks", keystore, "--ks-pass", f"pass:{password}", "--out", out, aligned)
|
||||
print(f"{os.path.relpath(out, ROOT)}: {os.path.getsize(out) // (1024 * 1024)} MB")
|
||||
if "--install" in sys.argv:
|
||||
run("adb", "install", "-r", out)
|
||||
run("adb", "shell", "am", "start", "-n", f"{PACKAGE}/.FryActivity")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
android/java/io/github/nexoniarz/fry3dx/FryActivity.java
Normal file
11
android/java/io/github/nexoniarz/fry3dx/FryActivity.java
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package io.github.nexoniarz.fry3dx;
|
||||
|
||||
import org.libsdl.app.SDLActivity;
|
||||
|
||||
// Fry3DX's activity: SDL's, loading SDL and the app (libmain.so, whose SDL_main it runs).
|
||||
public class FryActivity extends SDLActivity {
|
||||
@Override
|
||||
protected String[] getLibraries() {
|
||||
return new String[] {"SDL3", "main"};
|
||||
}
|
||||
}
|
||||
BIN
android/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
android/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
BIN
android/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
android/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
BIN
android/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
android/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8 KiB |
BIN
android/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
android/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
android/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
android/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
20
android/shell.nix
Normal file
20
android/shell.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Android build environment for Fry3DX: SDK (platform 35, build-tools), NDK, adb, a JDK.
|
||||
# nix-shell android/shell.nix --run "python3 android/build_apk.py"
|
||||
{ pkgs ? import <nixpkgs> { config = { android_sdk.accept_license = true; allowUnfree = true; }; } }:
|
||||
let
|
||||
android = pkgs.androidenv.composeAndroidPackages {
|
||||
platformVersions = [ "35" ];
|
||||
buildToolsVersions = [ "35.0.0" ];
|
||||
includeNDK = true;
|
||||
ndkVersions = [ "27.2.12479018" ];
|
||||
includeEmulator = false;
|
||||
includeSystemImages = false;
|
||||
};
|
||||
sdk = android.androidsdk;
|
||||
in pkgs.mkShell {
|
||||
packages = [ sdk pkgs.jdk17 pkgs.cmake pkgs.ninja pkgs.python3 pkgs.glslang pkgs.shaderc pkgs.zip pkgs.unzip ];
|
||||
ANDROID_HOME = "${sdk}/libexec/android-sdk";
|
||||
ANDROID_SDK_ROOT = "${sdk}/libexec/android-sdk";
|
||||
ANDROID_NDK_ROOT = "${sdk}/libexec/android-sdk/ndk/27.2.12479018";
|
||||
JAVA_HOME = "${pkgs.jdk17.home}";
|
||||
}
|
||||
Loading…
Reference in a new issue