From 5e674e1410edcc76c42fc6b4b380a0fc9861be4a Mon Sep 17 00:00:00 2001 From: Julian Winkler Date: Sat, 13 Apr 2024 16:34:21 +0200 Subject: [PATCH] add option to install desktop entry with dynamic launcher portal A desktop entry is needed to have window icons on Wayland. --- meson.build | 2 +- src/api-impl/android/app/Application.java | 5 ++ src/main-executable/main.c | 93 ++++++++++++++++++++++- 3 files changed, 95 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 9cb915cb..c9a3cfcf 100644 --- a/meson.build +++ b/meson.build @@ -149,7 +149,7 @@ executable('android-translation-layer', [ ], install: true, dependencies: [ - dependency('gtk4'), dependency('jni'), declare_dependency(link_with: libtranslationlayer_so), libart_dep, dependency('dl'), libdl_bio_dep + dependency('gtk4'), dependency('jni'), declare_dependency(link_with: libtranslationlayer_so), libart_dep, dependency('dl'), libdl_bio_dep, dependency('libportal') ], link_args: [ '-rdynamic' diff --git a/src/api-impl/android/app/Application.java b/src/api-impl/android/app/Application.java index 882d2b29..55a0c100 100644 --- a/src/api-impl/android/app/Application.java +++ b/src/api-impl/android/app/Application.java @@ -20,6 +20,11 @@ public class Application extends ContextWrapper { return app_icon_path; } + private String get_app_label() { + int app_label_resid = manifest.getApplicationElement().searchAttributeByResourceId(AndroidManifestBlock.ID_label).getData(); + return getResources().getString(app_label_resid); + } + public interface ActivityLifecycleCallbacks { void onActivityCreated(Activity activity, Bundle savedInstanceState); void onActivityStarted(Activity activity); diff --git a/src/main-executable/main.c b/src/main-executable/main.c index 778b1ac2..2aac6148 100644 --- a/src/main-executable/main.c +++ b/src/main-executable/main.c @@ -3,6 +3,7 @@ #include #include +#include #include "../api-impl-jni/defines.h" #include "../api-impl-jni/util.h" @@ -133,6 +134,28 @@ gboolean hacky_on_window_focus_changed_callback(JNIEnv *env) return G_SOURCE_CONTINUE; } +struct dynamic_launcher_callback_data {char *desktop_file_id; char *desktop_entry;}; +static void dynamic_launcher_ready_callback(GObject *portal, GAsyncResult *res, gpointer user_data) { + struct dynamic_launcher_callback_data *data = user_data; + GVariant *result = xdp_portal_dynamic_launcher_prepare_install_finish(XDP_PORTAL(portal), res, NULL); + if (!result) { + printf("cancelled\n"); + exit(0); + } + const char *token; + g_variant_lookup(result, "token", "s", &token); + GError *err = NULL; + xdp_portal_dynamic_launcher_install(XDP_PORTAL(portal), token, data->desktop_file_id, data->desktop_entry, &err); + g_free(data->desktop_file_id); + g_free(data->desktop_entry); + g_free(data); + if (err) { + printf("failed to install dynamic launcher: %s\n", err->message); + exit(1); + } + exit(0); +} + // this is exported by the shim bionic linker void dl_parse_library_path(const char *path, char *delim); @@ -147,7 +170,7 @@ void dl_parse_library_path(const char *path, char *delim); #define MICROG_APK_PATH_LOCAL "./com.google.android.gms.apk" #define FRAMEWORK_RES_PATH_LOCAL "./res/framework-res.apk" -struct jni_callback_data { char *apk_main_activity_class; uint32_t window_width; uint32_t window_height;}; +struct jni_callback_data { char *apk_main_activity_class; uint32_t window_width; uint32_t window_height; gboolean install; char *prgname; }; static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* hint, struct jni_callback_data *d) { //TODO: pass all files to classpath @@ -351,8 +374,6 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h if(getenv("ATL_FORCE_FULLSCREEN")) gtk_window_fullscreen(GTK_WINDOW(window)); - gtk_window_present(GTK_WINDOW(window)); - // construct Application application_object = (*env)->CallStaticObjectMethod(env, handle_cache.context.class, _STATIC_METHOD(handle_cache.context.class, "createApplication", "(J)Landroid/app/Application;"), window); @@ -392,10 +413,64 @@ static void open(GtkApplication *app, GFile** files, gint nfiles, const gchar* h if((*env)->ExceptionCheck(env)) (*env)->ExceptionDescribe(env); + if (d->install) { + XdpPortal *portal = xdp_portal_new(); + + const char *app_label = _CSTRING((*env)->CallObjectMethod(env, application_object, _METHOD(handle_cache.application.class, "get_app_label", "()Ljava/lang/String;"))); + if((*env)->ExceptionCheck(env)) + (*env)->ExceptionDescribe(env); + + GVariant *icon_serialized = NULL; + if (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); + 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); + } + + GString *desktop_entry = g_string_new( + "[Desktop Entry]\n" + "Type=Application\n" + "Exec=env "); + if (getenv("RUN_FROM_BUILDDIR")) { + printf("WARNING: RUN_FROM_BUILDDIR set and --install given: using current directory in desktop entry\n"); + g_string_append_printf(desktop_entry, "-C %s ", g_get_current_dir()); + } + char *envs[] = {"RUN_FROM_BUILDDIR", "LD_LIBRARY_PATH", "ANDROID_APP_DATA_DIR", "ATL_DISABLE_WINDOW_DECORATIONS", "UGLY_HACK_FOR_VR", "ATL_FORCE_FULLSCREEN"}; + for (int i = 0; i < sizeof(envs)/sizeof(envs[0]); i++) { + if (getenv(envs[i])) { + g_string_append_printf(desktop_entry, "%s=%s ", envs[i], getenv(envs[i])); + } + } + g_string_append_printf(desktop_entry, "%s ", d->prgname); + g_string_append_printf(desktop_entry, "--gapplication-app-id %s ", package_name); + if (d->apk_main_activity_class) + g_string_append_printf(desktop_entry, "-l %s ", d->apk_main_activity_class); + if (d->window_width) + g_string_append_printf(desktop_entry, "-w %d ", d->window_width); + if (d->window_height) + g_string_append_printf(desktop_entry, "-h %d ", d->window_height); + g_string_append_printf(desktop_entry, "%s\n", apk_classpath); + struct dynamic_launcher_callback_data *cb_data = g_new(struct dynamic_launcher_callback_data, 1); + cb_data->desktop_file_id = g_strdup_printf("%s.desktop", package_name); + cb_data->desktop_entry = g_string_free(desktop_entry, FALSE); + printf("installing %s\n\n%s\n", cb_data->desktop_file_id, cb_data->desktop_entry); + xdp_portal_dynamic_launcher_prepare_install(portal, NULL, app_label, icon_serialized, XDP_LAUNCHER_APPLICATION, NULL, TRUE, TRUE, NULL, dynamic_launcher_ready_callback, cb_data); + return; + } + gtk_window_set_title(GTK_WINDOW(window), package_name); gtk_window_set_default_size(GTK_WINDOW(window), d->window_width, d->window_height); g_signal_connect(window, "close-request", G_CALLBACK (app_exit), env); + gtk_window_present(GTK_WINDOW(window)); + // set package name as application id for window icon on Wayland. Needs a {package_name}.desktop file defining the icon GdkToplevel *toplevel = GDK_TOPLEVEL(gtk_native_get_surface(GTK_NATIVE(window))); if (GDK_IS_WAYLAND_TOPLEVEL(toplevel)) { @@ -472,6 +547,14 @@ void init_cmd_parameters(GApplication *app, struct jni_callback_data *d) .description = "window height to launch with (some apps react poorly to runtime window size adjustments)", .arg_description = NULL, }, + { + .long_name = "install", + .short_name = 'i', + .flags = 0, + .arg = G_OPTION_ARG_NONE, + .arg_data = &d->install, + .description = "install .desktop file for the given apk", + }, {NULL} }; @@ -497,8 +580,10 @@ int main(int argc, char **argv) callback_data->apk_main_activity_class = NULL; callback_data->window_width = 960; callback_data->window_height = 540; + callback_data->install = FALSE; + callback_data->prgname = argv[0]; - app = gtk_application_new("com.example.demo_application", G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN); + app = gtk_application_new("com.example.demo_application", G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN | G_APPLICATION_CAN_OVERRIDE_APP_ID); // cmdline related setup init_cmd_parameters(G_APPLICATION(app), callback_data);