main-executable: handle drawable-based app icons by rendering them into SVG

This commit is contained in:
Mis012
2025-06-21 01:22:29 +02:00
parent 1c83948b15
commit 2a0c6cd455
6 changed files with 63 additions and 13 deletions

View File

@@ -35,6 +35,7 @@
#define _GET_FLOAT_FIELD(object, field) ((*env)->GetFloatField(env, object, _FIELD_ID(_CLASS(object), field, "F"))) #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_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_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 _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_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)) #define _GET_BYTE_ARRAY_ELEMENTS(b_array) ((*env)->GetByteArrayElements(env, b_array, NULL))

View File

@@ -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.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_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.class = _REF((*env)->FindClass(env, "android/os/Looper"));
handle_cache.looper.loop = _STATIC_METHOD(handle_cache.looper.class, "loop", "()V"); handle_cache.looper.loop = _STATIC_METHOD(handle_cache.looper.class, "loop", "()V");

View File

@@ -107,6 +107,7 @@ struct handle_cache {
struct { struct {
jclass class; jclass class;
jmethodID get_app_icon_path; jmethodID get_app_icon_path;
jmethodID get_app_icon_paintable;
} application; } application;
struct { struct {
jclass class; jclass class;

View File

@@ -2,6 +2,7 @@ package android.app;
import android.os.Bundle; import android.os.Bundle;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.R;
import android.content.Context; import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.pm.PackageParser; import android.content.pm.PackageParser;
@@ -10,7 +11,22 @@ public class Application extends ContextWrapper {
public long native_window; public long native_window;
private String get_app_icon_path() { 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() { String get_app_label() {

View File

@@ -2613,7 +2613,7 @@ public class PackageManager {
* @see #getApplicationIcon(String) * @see #getApplicationIcon(String)
*/ */
public Drawable getApplicationIcon(ApplicationInfo info) { public Drawable getApplicationIcon(ApplicationInfo info) {
return null; return Context.this_application.getDrawable(info.icon);
} }
/** /**

View File

@@ -1,6 +1,7 @@
// for dladdr // for dladdr
#define _GNU_SOURCE #define _GNU_SOURCE
#include <cairo-svg.h>
#include <gdk/wayland/gdkwayland.h> #include <gdk/wayland/gdkwayland.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <libportal/portal.h> #include <libportal/portal.h>
@@ -176,6 +177,13 @@ static void dynamic_launcher_ready_callback(GObject *portal, GAsyncResult *res,
exit(0); 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 // this is exported by the shim bionic linker
void dl_parse_library_path(const char *path, char *delim); void dl_parse_library_path(const char *path, char *delim);
@@ -561,7 +569,9 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h
(*env)->ExceptionDescribe(env); (*env)->ExceptionDescribe(env);
GVariant *icon_serialized = NULL; GVariant *icon_serialized = NULL;
if (app_icon_path && !d->install_internal) { if (!d->install_internal) {
if (app_icon_path) {
/* we can import the icon as-is */
extract_from_apk(app_icon_path, app_icon_path); 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); 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); GMappedFile *icon_file = g_mapped_file_new(app_icon_path_full, FALSE, NULL);
@@ -572,6 +582,27 @@ static void open(GtkApplication *app, GFile **files, gint nfiles, const gchar *h
g_bytes_unref(icon_bytes); g_bytes_unref(icon_bytes);
g_mapped_file_unref(icon_file); g_mapped_file_unref(icon_file);
g_free(app_icon_path_full); 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); gchar *dest_name = g_strdup_printf("%s.apk", package_name);