add option to install desktop entry with dynamic launcher portal

A desktop entry is needed to have window icons on Wayland.
This commit is contained in:
Julian Winkler
2024-04-13 16:34:21 +02:00
parent 1150402e38
commit 5e674e1410
3 changed files with 95 additions and 5 deletions

View File

@@ -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'

View File

@@ -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);

View File

@@ -3,6 +3,7 @@
#include <gtk/gtk.h>
#include <gdk/wayland/gdkwayland.h>
#include <libportal/portal.h>
#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);