Skip to content

Commit 39e948e

Browse files
authored
fix: Added progress bar to first time GUI download + Switched from materials icon to materials symbol + Cleaned up home screen code (MorpheApp#234)
- Added a progress bar to the initial download of the gui from the jar file. - Surface first run download failures instead of exiting silently. A failed gui download or checksum mismatch used to call exitProcess(1) behind only a log line, so a gui user just saw the app never open. It now throws BootstrapException and shows a "Setup failed" window with the reason and a quit button. - Moved the material icons back to the jar file so ProGuard can tree-shake it to just the 50 odd icons we actually use. This drops another 36mb in size.
1 parent 4c436d5 commit 39e948e

47 files changed

Lines changed: 4135 additions & 4053 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.run/Morphe Desktop.run.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<component name="ProjectRunConfigurationManager">
22
<configuration default="false" name="Morphe Desktop" type="Application" factoryName="Application">
3-
<option name="ALTERNATIVE_JRE_PATH" value="temurin-17" />
3+
<option name="ALTERNATIVE_JRE_PATH" value="temurin-21" />
44
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
55
<option name="MAIN_CLASS_NAME" value="app.morphe.MorpheLauncherKt" />
66
<module name="morphe-desktop.main" />

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ All modifications made by Morphe can be found in the Git history.
3030
1. Java Runtime Environment 21 or above ([Azul Zulu JRE](https://www.azul.com/downloads/?version=java-21-lts&package=jre#zulu), [Temurin](https://adoptium.net/temurin/releases?version=21&os=any&arch=any) or [OpenJDK](https://jdk.java.net/archive/)).
3131
2. Morphe Desktop jar file (morphe-desktop-*-all.jar). Download the latest from [here](https://github.com/MorpheApp/morphe-desktop/releases/latest).
3232
3. If using CLI: Patches mpp file (patches-*.mpp). Download the latest Morphe official patch files from [here](https://github.com/MorpheApp/morphe-patches/releases/latest).
33-
4. If using CLI: Desired app file (app.apk or app.apkm). Download your apk from [APK Mirror](https://www.apkmirror.com/).
33+
4. Desired app file (app.apk or app.apkm). Download your apk from [APK Mirror](https://www.apkmirror.com/).
3434
5. [Optional] [Android Debug Bridge (ADB)](https://developer.android.com/studio/command-line/adb) If you want to install the patched APK file to your device directly from your computer.
3535

3636
## Documentation
@@ -60,6 +60,9 @@ The GUI is far more user-friendly and straightforward than the CLI. On your firs
6060

6161
![Morphe GUI Home Screen](docs/images/main_readme/home_screen.png)
6262

63+
> [!NOTE]
64+
> **First launch only.** Morphe ships as a single small jar. The command-line tool runs on its own, but the GUI needs a couple of platform-specific components (the Skiko renderer and JNA) that aren't bundled, so the download stays small. The **first** time you open the GUI, Morphe fetches the right components for your operating system into `morphe-data/libs/` and shows a brief setup window before the home screen appears. This happens only once. Later launches reuse the cached files and need no internet connection.
65+
6366
2. Drag and drop your `.apk` (or `.apkm` / `.xapk` / `.apks` bundle) into the window. Once it's analyzed, click **PATCH** to begin:
6467

6568
![Morphe GUI App Selected](docs/images/main_readme/app_selected.png)

build.gradle.kts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ dependencies {
8686
implementation(libs.picocli)
8787

8888
// -- Bootstrap (Code Generation) ---------------------------------------
89-
bootstrapDependencies("org.jetbrains.compose.material:material-icons-extended-desktop:${libs.versions.materialIcons.get()}")
9089
bootstrapDependencies("net.java.dev.jna:jna:${libs.versions.jna.get()}")
9190
bootstrapDependencies("net.java.dev.jna:jna-platform:${libs.versions.jna.get()}")
9291
bootstrapDependencies("org.jetbrains.skiko:skiko-awt-runtime-macos-x64:${libs.versions.skiko.get()}")
@@ -106,7 +105,6 @@ dependencies {
106105
implementation("org.jetbrains.compose.components:components-resources:${libs.versions.compose.get()}")
107106
@Suppress("DEPRECATION")
108107
implementation(compose.material3)
109-
implementation("org.jetbrains.compose.material:material-icons-extended-desktop:1.7.3")
110108

111109
// -- Async / Serialization ---------------------------------------------
112110
implementation(libs.kotlinx.coroutines.core)
@@ -167,11 +165,9 @@ tasks {
167165
inputs.files(artifactFiles)
168166

169167
val skikoVersion = libs.versions.skiko.get()
170-
val materialIconsVersion = libs.versions.materialIcons.get()
171168
val jnaVersion = libs.versions.jna.get()
172-
169+
173170
inputs.property("skikoVersion", skikoVersion)
174-
inputs.property("materialIconsVersion", materialIconsVersion)
175171
inputs.property("jnaVersion", jnaVersion)
176172

177173
val outputDir = layout.buildDirectory.dir("generated/source/bootstrap/main/app/morphe/engine")
@@ -195,10 +191,8 @@ tasks {
195191
196192
internal object BootstrapConstants {
197193
const val SKIKO_VERSION = "$skikoVersion"
198-
const val MATERIAL_ICONS_VERSION = "$materialIconsVersion"
199194
const val JNA_VERSION = "$jnaVersion"
200-
201-
const val MATERIAL_ICONS_HASH = "${getHash("material-icons-extended-desktop", materialIconsVersion)}"
195+
202196
const val JNA_HASH = "${getHash("jna", jnaVersion)}"
203197
const val JNA_PLATFORM_HASH = "${getHash("jna-platform", jnaVersion)}"
204198
@@ -286,13 +280,12 @@ tasks {
286280
}
287281
}
288282

289-
// -------------------------------------------------------------------------
283+
// ============================================================================
290284
// Shadow JAR — the only distribution artifact
291-
// -------------------------------------------------------------------------
285+
// ============================================================================
292286
shadowJar {
293287
dependencies {
294288
exclude(dependency("org.jetbrains.skiko:skiko-awt-runtime-.*:.*"))
295-
exclude(dependency("org.jetbrains.compose.material:material-icons-extended-desktop:.*"))
296289
exclude(dependency("net.java.dev.jna:jna:.*"))
297290
exclude(dependency("net.java.dev.jna:jna-platform:.*"))
298291
}

docs/documentation.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,22 @@ Now that you've done your first run, let's dig deeper into how the magic happens
4545
Morphe keeps its runtime data: cached patch files, logs, scratch space for patching, and the default signing keystore in a single **`morphe-data/`** folder. By default, this folder is created **next to the JAR** you run, so it survives upgrades and is easy to find.
4646
If that location isn't writable (e.g. running from an IDE, or from a read-only install path), Morphe falls back to **`~/morphe/`**. The startup logs print which one is in use (look for `Morphe data root: ...`).
4747

48+
You can override the location entirely with the **`MORPHE_DATA_DIR`** environment variable. Point it at a writable directory and Morphe uses that as the data root, ahead of both the JAR-adjacent default and the `~/morphe/` fallback. This is meant for read-only or package-manager installs, where the JAR's own folder isn't writable and you'd rather choose the location than land in `~/morphe/`. If the path you set isn't writable, Morphe logs a warning and ignores it.
49+
4850
```
4951
morphe-data/
5052
patches/ # cached .mpp patch bundles
5153
logs/ # application logs
5254
tmp/ # per-run patching scratch (the default --temporary-files-path location)
55+
libs/ # GUI runtime (Skiko, JNA) downloaded on the first GUI launch
5356
morphe.keystore # shared default signing key (see --keystore)
5457
config.json # preferences + configured sources
5558
```
5659

5760
Both the GUI and the CLI use this folder. In the GUI you can open it from **Tools → Open App Data**. On the CLI, `--temporary-files-path` defaults to `tmp/` (override it to send scratch elsewhere) and `--keystore` defaults to `morphe.keystore` here.
5861

62+
The **`libs/`** folder is special: it only appears once you open the **GUI**. Morphe ships as a single small jar, and to keep that download small it does not bundle the GUI's platform-specific rendering library (Skiko) or its native-access library (JNA). The first time you launch the GUI, Morphe downloads the correct builds for your operating system and architecture from Maven Central, verifies each one by checksum, and caches them here while showing a brief setup window. The CLI needs none of these, so running from a terminal never downloads anything. Every later GUI launch reuses this cache and works offline.
63+
5964
> [!TIP]
6065
> **Choosing a custom location.** If neither default suits you (for example, a package-manager install lands the JAR in a read-only, system-wide path), set the **`MORPHE_DATA_DIR`** environment variable to a writable directory. When it's set and usable, Morphe uses it as the data root ahead of everything else. If the value isn't a writable directory, Morphe logs a warning and ignores it. On Linux, if `MORPHE_DATA_DIR` is unset but `XDG_DATA_HOME` is exported, a *fallback* install uses `$XDG_DATA_HOME/morphe`. An existing `~/morphe/` folder is always kept as-is so upgrades never strand your data. Either way, the startup log line (`Morphe data root: ...`) tells you which location won.
6166
@@ -118,6 +123,9 @@ The home screen has a top bar (on the top):
118123
| 🟡 | **STABLE OLDER** | a stable release, but a newer one exists |
119124
| 🔵 | **DEV LATEST** | newest pre-release (experimental) |
120125
| 🔴 | **DEV OLDER** | a pre-release but behind |
126+
| 🩵 | **LOCAL** | a local `.mpp` file or folder source |
127+
128+
A **disabled** source shows a **dimmed** LED, and a source that **failed to load** shows a **red** LED and raises an error banner. Those two states take priority over the channel color.
121129

122130
In Expert mode, click the pill to open the **source manager** – add, remove, refresh, or reorder sources (see [Managing patch sources](#gui-expert-sources)). In Quick mode it's informational. The same LED colors appear on each row inside the source manager.
123131

@@ -191,6 +199,8 @@ The sources Morphe fetches from are configurable: add community sources (GitHub
191199

192200
**Channel badge.** Each enabled source shows a small badge for the release it resolved to, color-coded by release channel, the same colors (and meanings) as the sources-pill LEDs in [The TopBar](#gui-window).
193201

202+
**When a source fails to load.** Morphe no longer spins forever on a source it can't fetch or parse. It stops with an error, turns that source's badge and LED **red**, and shows a banner naming the source and the reason, so you can fix or remove it while the other sources still load.
203+
194204
**Local sources.** A source can also be a local `.mpp` file on disk. If you're *building* patches, enable [Developer options](#gui-settings-developer) to point a source at a **folder** instead. Morphe then always loads the newest `.mpp` in it, so a rebuild is picked up without having to re-select the file.
195205

196206
<p align="center">
@@ -386,6 +396,7 @@ The gear icon opens Settings – Morphe's persistent preferences. Think of it as
386396
| **Strip Libs** | Native-lib architectures to keep | Keep all | `--striplibs` |
387397
| **Patched app runtime logs** | Capture logcat from a device after a patched app misbehaves |||
388398
| **Route links to patched app** | After an ADB install, auto-route the app's web links to it (with an optional "disable stock app's links") | Off | [`utility install --route-links`](#utility-install) |
399+
| **Developer options** | Unlock patch-developer workflows (currently: point a local source at a folder so its newest .mpp auto-loads) | Off ||
389400

390401
<p align="center">
391402
<img src="images/documentation/gui/settings.png" width="60%" alt="Settings dialog" style="vertical-align: middle"/>
@@ -454,7 +465,7 @@ A debugging aid: capture logcat from a connected device after a patched app cras
454465

455466
This section is for people **building** patches and other developers. Off by default. Toggling it on unlocks a suite of workflow options to better assist you in your development. Please feel free to make a request in the issues tab if you feel a feature could be added here.
456467

457-
**Folder sources.** Normally a local source points at a single `.mpp` file. With Developer options on, the add/edit source dialog gains a **FOLDER** button next to **FILE**. Pick a folder and Morphe always loads the **newest `.mpp`** inside it. Point it at your patch build-output directory once and you never re-pick the file: each rebuild is used on the next load. The [source sheet](#gui-expert-sources) also gains a **refresh** button (top-right) that re-scans on demand, so a patch you just built shows up without leaving the screen.
468+
**Folder sources.** Normally a local source points at a single `.mpp` file. With Developer options on, the add/edit source dialog gains a **FOLDER** button next to **FILE**. Pick a folder and Morphe always loads the **newest `.mpp`** inside it. Point it at your patch build-output directory once and you never re-pick the file: each rebuild is used on the next load. The [source sheet](#gui-expert-sources) also gains a **refresh** button (top-right) that re-scans on demand, so a patch you just built shows up without leaving the screen. Build outputs like `*-sources.mpp` and `*-javadoc.mpp` are always ignored, and you can set your own exclude patterns for a folder source from the source manager.
458469

459470
> [!NOTE]
460471
> Existing folder sources keep working even if you later turn Developer options back off. The toggle only gates *creating* them.
@@ -470,7 +481,7 @@ The wrench icon opens Tools – one-off actions and reference info, kept out of
470481
| **OPEN APP DATA** | Open Morphe's data folder – the`morphe-data/` directory (see [Where Morphe stores its files](#where-files-stored)) |
471482
| **CLEAR CACHE** | Delete downloaded patches and logs (they re-download as needed) |
472483
| **VIEW LICENSES** | Browse the open-source licenses of Morphe's dependencies |
473-
| Version | The running app version is shown at the bottom |
484+
| Version | The running app version, plus the bundled **Morphe Patcher** and **Morphe Library** versions, are shown at the bottom |
474485

475486
<p align="center">
476487
<img src="images/documentation/gui/tools.png" width="60%" alt="Tools Setting" style="vertical-align: middle"/>
@@ -665,6 +676,9 @@ Default: a subfolder named after the app, created next to the input APK – `<ap
665676
666677
Specify a custom output path for the patched APK.
667678
679+
> [!NOTE]
680+
> `<appVersion>` in the default name is read from the APK's manifest (`versionName`). Patching two different versions of the same app therefore produces two differently-named files instead of overwriting each other, even when the input files happen to share a name (for example both called `base.apk`).
681+
668682
```
669683
java -jar morphe-desktop-*-all.jar patch -p patches.mpp -o /path/to/output.apk your_app.apk
670684
```

gradle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ version = 1.13.0-dev.7
88
# (~4g total) fits comfortably on a 16GB machine.
99
org.gradle.jvmargs = -Xmx2g
1010
kotlin.daemon.jvmargs = -Xmx2g
11+
12+
# Enabled parallel sync for Gradle 9.4+
13+
org.gradle.tooling.parallel=true

scripts/add-icon.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Generates a MorpheIcons entry for a Material Symbols icon.
4+
#
5+
# Usage: scripts/add-icon.sh <CamelCaseName> [style]
6+
# Example: scripts/add-icon.sh CloudDownload
7+
# scripts/add-icon.sh Bookmark outline
8+
# scripts/add-icon.sh Settings rounded
9+
#
10+
# style (optional, default "filled"):
11+
# filled | outline | rounded | sharp | rounded-outline | sharp-outline
12+
#
13+
# Non-filled styles suffix the val name (Bookmark + outline -> BookmarkOutline),
14+
# so several variants of the same icon can live side by side.
15+
#
16+
# It fetches the SVG from Google and prints the exact line to paste into the
17+
# MorpheIcons object in src/main/kotlin/app/morphe/gui/ui/icons/MorpheIcons.kt
18+
#
19+
# If it prints "Not found", check the exact name at https://fonts.google.com/icons
20+
21+
set -euo pipefail
22+
23+
name="${1:-}"
24+
style="${2:-filled}"
25+
if [ -z "$name" ]; then
26+
echo "Usage: $0 <CamelCaseName> [filled|outline|rounded|sharp|rounded-outline|sharp-outline]" >&2
27+
exit 1
28+
fi
29+
30+
# Map the style to a Material Symbols family + fill + a name suffix.
31+
case "$style" in
32+
filled) family="outlined"; fill="fill1"; suffix="" ;;
33+
outline) family="outlined"; fill="default"; suffix="Outline" ;;
34+
rounded) family="rounded"; fill="fill1"; suffix="Rounded" ;;
35+
sharp) family="sharp"; fill="fill1"; suffix="Sharp" ;;
36+
rounded-outline) family="rounded"; fill="default"; suffix="RoundedOutline" ;;
37+
sharp-outline) family="sharp"; fill="default"; suffix="SharpOutline" ;;
38+
*) echo "Unknown style '$style'. Use: filled|outline|rounded|sharp|rounded-outline|sharp-outline" >&2; exit 1 ;;
39+
esac
40+
41+
# CamelCase -> snake_case for Google's icon id (CloudDownload -> cloud_download).
42+
snake=$(printf '%s' "$name" | sed -E 's/([a-z0-9])([A-Z])/\1_\2/g' | tr '[:upper:]' '[:lower:]')
43+
val="${name}${suffix}"
44+
url="https://fonts.gstatic.com/s/i/short-term/release/materialsymbols${family}/${snake}/${fill}/24px.svg"
45+
46+
svg=$(curl -fsSL "$url" 2>/dev/null || true)
47+
if [ -z "$svg" ]; then
48+
echo "Not found ('$snake', $style). Check the exact name at https://fonts.google.com/icons" >&2
49+
exit 1
50+
fi
51+
52+
# Every <path d="..."> becomes a quoted, comma-separated arg (symbol() is vararg).
53+
args=$(printf '%s' "$svg" | grep -oE 'd="[^"]*"' | sed 's/^d=//' | paste -sd, - | sed 's/,/, /g')
54+
55+
echo "Paste into the MorpheIcons object in MorpheIcons.kt:"
56+
echo ""
57+
printf ' val %s: ImageVector by lazy { symbol("%s", %s) }\n' "$val" "$val" "$args"

0 commit comments

Comments
 (0)