From 2a0c6cd45597e153c4dc2c2334f5d2936724665f Mon Sep 17 00:00:00 2001 From: Mis012 Date: Sat, 21 Jun 2025 01:22:29 +0200 Subject: [PATCH] main-executable: handle drawable-based app icons by rendering them into SVG --- src/api-impl-jni/defines.h | 1 + src/api-impl-jni/util.c | 1 + src/api-impl-jni/util.h | 1 + src/api-impl/android/app/Application.java | 18 ++++++- .../android/content/pm/PackageManager.java | 2 +- src/main-executable/main.c | 53 +++++++++++++++---- 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/api-impl-jni/defines.h b/src/api-impl-jni/defines.h index 3e1cd807..1634f142 100644 --- a/src/api-impl-jni/defines.h +++ b/src/api-impl-jni/defines.h @@ -35,6 +35,7 @@ #define _GET_FLOAT_FIELD(object, field) ((*env)->GetFloatField(env, object, _FIELD_ID(_CLASS(object), field, "F"))) #define _SET_FLOAT_FIELD(object, field, value) ((*env)->SetFloatField(env, object, _FIELD_ID(_CLASS(object), field, "F"), value)) #define _SET_STATIC_INT_FIELD(class, field, value) ((*env)->SetStaticIntField(env, class, _STATIC_FIELD_ID(class, field, "I"), value)) +#define _SET_STATIC_BOOL_FIELD(class, field, value) ((*env)->SetStaticBooleanField(env, class, _STATIC_FIELD_ID(class, field, "Z"), value)) #define _SET_STATIC_OBJ_FIELD(class, field, type, value) ((*env)->SetStaticObjectField(env, class, _STATIC_FIELD_ID(class, field, type), value)) #define _GET_STATIC_OBJ_FIELD(class, field, type) ((*env)->GetStaticObjectField(env, class, _STATIC_FIELD_ID(class, field, type))) #define _GET_BYTE_ARRAY_ELEMENTS(b_array) ((*env)->GetByteArrayElements(env, b_array, NULL)) diff --git a/src/api-impl-jni/util.c b/src/api-impl-jni/util.c index 3a8845fd..0fc66fe5 100644 --- a/src/api-impl-jni/util.c +++ b/src/api-impl-jni/util.c @@ -162,6 +162,7 @@ void set_up_handle_cache(JNIEnv *env) handle_cache.application.class = _REF((*env)->FindClass(env, "android/app/Application")); handle_cache.application.get_app_icon_path = _METHOD(handle_cache.application.class, "get_app_icon_path", "()Ljava/lang/String;"); + handle_cache.application.get_app_icon_paintable = _METHOD(handle_cache.application.class, "get_app_icon_paintable", "()J"); handle_cache.looper.class = _REF((*env)->FindClass(env, "android/os/Looper")); handle_cache.looper.loop = _STATIC_METHOD(handle_cache.looper.class, "loop", "()V"); diff --git a/src/api-impl-jni/util.h b/src/api-impl-jni/util.h index 784a6f27..613d7636 100644 --- a/src/api-impl-jni/util.h +++ b/src/api-impl-jni/util.h @@ -107,6 +107,7 @@ struct handle_cache { struct { jclass class; jmethodID get_app_icon_path; + jmethodID get_app_icon_paintable; } application; struct { jclass class; diff --git a/src/api-impl/android/app/Application.java b/src/api-impl/android/app/Application.java index 5e81cb64..7061bef8 100644 --- a/src/api-impl/android/app/Application.java +++ b/src/api-impl/android/app/Application.java @@ -2,6 +2,7 @@ package android.app; import android.os.Bundle; import android.content.res.Configuration; +import android.R; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageParser; @@ -10,7 +11,22 @@ public class Application extends ContextWrapper { public long native_window; private String get_app_icon_path() { - return getString(pkg.applicationInfo.icon); + String icon_path = null; + try { + icon_path = getString(pkg.applicationInfo.icon); + } catch (android.content.res.Resources.NotFoundException e) { + e.printStackTrace(); + } + if (icon_path == null) { + icon_path = getString(R.mipmap.sym_def_app_icon); + } else if (icon_path.endsWith(".xml")) { + icon_path = null; + } + return icon_path; + } + + private long get_app_icon_paintable() { + return getPackageManager().getApplicationIcon(pkg.applicationInfo).paintable; } String get_app_label() { diff --git a/src/api-impl/android/content/pm/PackageManager.java b/src/api-impl/android/content/pm/PackageManager.java index 0041b006..b463bfda 100644 --- a/src/api-impl/android/content/pm/PackageManager.java +++ b/src/api-impl/android/content/pm/PackageManager.java @@ -2613,7 +2613,7 @@ public class PackageManager { * @see #getApplicationIcon(String) */ public Drawable getApplicationIcon(ApplicationInfo info) { - return null; + return Context.this_application.getDrawable(info.icon); } /** diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 003f2926..04b84657 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -1,6 +1,7 @@ // for dladdr #define _GNU_SOURCE +#include #include #include #include @@ -176,6 +177,13 @@ static void dynamic_launcher_ready_callback(GObject *portal, GAsyncResult *res, exit(0); } +static cairo_status_t cairo_write_func_gstring(void *closure, const unsigned char *data, unsigned int length) +{ + GString *str = closure; + g_string_append_len(str, (gchar *)data, length); + return CAIRO_STATUS_SUCCESS; +} + // this is exported by the shim bionic linker void dl_parse_library_path(const char *path, char *delim); @@ -561,17 +569,40 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h (*env)->ExceptionDescribe(env); GVariant *icon_serialized = NULL; - if (app_icon_path && !d->install_internal) { - extract_from_apk(app_icon_path, app_icon_path); - char *app_icon_path_full = g_strdup_printf("%s/%s", app_data_dir, app_icon_path); - GMappedFile *icon_file = g_mapped_file_new(app_icon_path_full, FALSE, NULL); - GBytes *icon_bytes = g_mapped_file_get_bytes(icon_file); - GIcon *icon = g_bytes_icon_new(icon_bytes); - icon_serialized = g_icon_serialize(icon); - g_object_unref(icon); - g_bytes_unref(icon_bytes); - g_mapped_file_unref(icon_file); - g_free(app_icon_path_full); + if (!d->install_internal) { + if (app_icon_path) { + /* we can import the icon as-is */ + extract_from_apk(app_icon_path, app_icon_path); + char *app_icon_path_full = g_strdup_printf("%s/%s", app_data_dir, app_icon_path); + GMappedFile *icon_file = g_mapped_file_new(app_icon_path_full, FALSE, NULL); + GBytes *icon_bytes = g_mapped_file_get_bytes(icon_file); + GIcon *icon = g_bytes_icon_new(icon_bytes); + icon_serialized = g_icon_serialize(icon); + g_object_unref(icon); + g_bytes_unref(icon_bytes); + g_mapped_file_unref(icon_file); + g_free(app_icon_path_full); + } else { + /* the icon is a generalized Drawable, let's render it into an SVG */ + _SET_STATIC_BOOL_FIELD((*env)->FindClass(env, "android/graphics/drawable/VectorDrawable"), "direct_draw_override", true); + GdkPaintable *icon_paintable = _PTR((*env)->CallLongMethod(env, application_object, handle_cache.application.get_app_icon_paintable)); + GString *svg_string = g_string_new(""); + cairo_surface_t *svg_surface = cairo_svg_surface_create_for_stream(cairo_write_func_gstring, svg_string, 108, 108); + cairo_t *cr = cairo_create(svg_surface); + GdkSnapshot *snapshot = gtk_snapshot_new(); + gdk_paintable_snapshot(icon_paintable, snapshot, 108, 108); + GskRenderNode *node = gtk_snapshot_to_node(snapshot); + gsk_render_node_draw(node, cr); + gsk_render_node_unref(node); + g_object_unref(snapshot); + cairo_destroy(cr); + cairo_surface_destroy(svg_surface); + GBytes *icon_bytes = g_string_free_to_bytes(svg_string); + GIcon *icon = g_bytes_icon_new(icon_bytes); + icon_serialized = g_icon_serialize(icon); + g_object_unref(icon); + g_bytes_unref(icon_bytes); + } } gchar *dest_name = g_strdup_printf("%s.apk", package_name);