2022-10-12 17:23:19 +02:00
// for dladdr
# define _GNU_SOURCE
2024-04-13 14:47:55 +02:00
# include <gdk/wayland/gdkwayland.h>
2024-09-15 00:42:55 +02:00
# include <gtk/gtk.h>
2024-04-13 16:34:21 +02:00
# include <libportal/portal.h>
2022-07-23 00:06:32 +02:00
2024-09-15 00:42:55 +02:00
2022-10-02 23:06:56 +02:00
# include "../api-impl-jni/defines.h"
# include "../api-impl-jni/util.h"
2023-08-11 18:09:17 +02:00
# include "../api-impl-jni/app/android_app_Activity.h"
2022-07-23 00:06:32 +02:00
# include <dlfcn.h>
2022-10-02 23:06:56 +02:00
# include <errno.h>
2023-08-12 13:05:34 +02:00
# include <libgen.h>
2023-01-05 18:58:52 +01:00
# include <locale.h>
2022-10-02 23:06:56 +02:00
# include <stdio.h>
# include <sys/stat.h>
2022-07-23 00:06:32 +02:00
2022-11-02 14:34:45 +01:00
# ifndef DEFFILEMODE
2024-09-15 00:42:55 +02:00
# define DEFFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) /* 0666*/
2022-11-02 14:34:45 +01:00
# endif
2023-06-06 22:08:00 +02:00
# ifdef __x86_64__
# define NATIVE_ARCH "x86_64"
# elifdef __i386__
# define NATIVE_ARCH "x86"
# elifdef __aarch64__
# define NATIVE_ARCH "arm64-v8a"
# elifdef __arm__
# define NATIVE_ARCH "armeabi-v7a"
# else
# error unknown native architecture
# endif
2022-07-23 00:06:32 +02:00
GtkWidget * window ;
2024-06-19 20:47:49 +02:00
char * apk_path ;
2022-07-23 00:06:32 +02:00
// standard Gtk Application stuff, more or less
2024-09-15 00:42:55 +02:00
gboolean app_exit ( GtkWindow * self , JNIEnv * env ) // TODO: do more cleanup?
2022-07-23 00:06:32 +02:00
{
2023-08-11 18:09:17 +02:00
activity_close_all ( ) ;
2022-07-23 00:06:32 +02:00
return false ;
}
2022-10-12 17:23:19 +02:00
// this is the equivalent of /data/data/com.example.app/
char * app_data_dir = NULL ;
char * get_app_data_dir ( )
{
return app_data_dir ;
}
char * construct_classpath ( char * prefix , char * * cp_array , size_t len )
2022-07-23 00:06:32 +02:00
{
size_t result_len = strlen ( prefix ) ;
2024-09-15 00:42:55 +02:00
for ( int i = 0 ; i < len ; i + + ) {
if ( cp_array [ i ] )
2022-10-12 17:23:19 +02:00
result_len + = strlen ( cp_array [ i ] ) + 1 ; // the 1 is for either : or the final \0
2022-07-23 00:06:32 +02:00
}
char * result = malloc ( result_len ) ;
strcpy ( result , prefix ) ;
2024-09-15 00:42:55 +02:00
for ( int i = 0 ; i < len ; i + + ) {
if ( cp_array [ i ] ) {
2023-06-21 18:33:30 +02:00
if ( i > 0 )
2023-06-07 13:25:36 +02:00
strcat ( result , " : " ) ;
2023-06-21 18:33:30 +02:00
strcat ( result , cp_array [ i ] ) ;
2023-06-07 13:25:36 +02:00
}
2022-07-23 00:06:32 +02:00
}
2023-06-21 18:33:30 +02:00
printf ( " construct_classpath: returning >%s< \n " , result ) ;
2022-07-23 00:06:32 +02:00
return result ;
}
2024-02-09 16:01:57 +01:00
# define JDWP_ARG "-XjdwpOptions:transport=dt_socket,server=y,suspend=y,address="
2022-11-24 16:09:42 +01:00
2024-09-15 00:42:55 +02:00
JNIEnv * create_vm ( char * api_impl_jar , char * apk_classpath , char * microg_apk , char * framework_res_apk , char * api_impl_natives_dir , char * app_lib_dir , char * * extra_jvm_options )
{
JavaVM * jvm ;
JNIEnv * env ;
2024-05-08 20:03:58 +02:00
JavaVMInitArgs args = {
. version = JNI_VERSION_1_6 ,
. nOptions = 3 ,
} ;
JavaVMOption * options ;
int option_counter = args . nOptions ;
2024-03-11 15:15:21 +01:00
char jdwp_option_string [ sizeof ( JDWP_ARG ) + 5 ] = JDWP_ARG ; // 5 chars for port number, NULL byte is counted by sizeof
2022-11-24 16:09:42 +01:00
2024-09-15 00:42:55 +02:00
const char * jdwp_port = getenv ( " JDWP_LISTEN " ) ;
2024-05-08 20:03:58 +02:00
2024-09-15 00:42:55 +02:00
if ( jdwp_port )
2024-02-09 16:01:57 +01:00
args . nOptions + = 2 ;
2024-09-15 00:42:55 +02:00
if ( extra_jvm_options )
2024-05-08 20:03:58 +02:00
args . nOptions + = g_strv_length ( extra_jvm_options ) ;
options = malloc ( sizeof ( JavaVMOption ) * args . nOptions ) ;
2024-09-15 00:42:55 +02:00
if ( getenv ( " RUN_FROM_BUILDDIR " ) ) {
2022-10-12 17:23:19 +02:00
options [ 0 ] . optionString = construct_classpath ( " -Djava.library.path= " , ( char * [ ] ) { " ./ " , app_lib_dir } , 2 ) ;
} else {
options [ 0 ] . optionString = construct_classpath ( " -Djava.library.path= " , ( char * [ ] ) { api_impl_natives_dir , app_lib_dir } , 2 ) ;
}
2023-07-14 20:37:10 +02:00
options [ 1 ] . optionString = construct_classpath ( " -Djava.class.path= " , ( char * [ ] ) { api_impl_jar , apk_classpath , microg_apk , framework_res_apk } , 4 ) ;
2024-05-08 20:03:58 +02:00
options [ 2 ] . optionString = " -Xcheck:jni " ;
2024-09-15 00:42:55 +02:00
if ( jdwp_port ) {
2022-11-24 16:09:42 +01:00
strncat ( jdwp_option_string , jdwp_port , 5 ) ; // 5 chars is enough for a port number, and won't overflow our array
2024-05-08 20:03:58 +02:00
options [ option_counter + + ] . optionString = " -XjdwpProvider:internal " ;
options [ option_counter + + ] . optionString = jdwp_option_string ;
}
2024-09-15 00:42:55 +02:00
while ( extra_jvm_options & & * extra_jvm_options ) {
2024-05-08 20:03:58 +02:00
options [ option_counter + + ] . optionString = * ( extra_jvm_options + + ) ;
2022-11-24 16:09:42 +01:00
}
2022-07-23 00:06:32 +02:00
args . options = options ;
args . ignoreUnrecognized = JNI_FALSE ;
int ret = JNI_CreateJavaVM ( & jvm , ( void * * ) & env , & args ) ;
2024-09-15 00:42:55 +02:00
if ( ret < 0 ) {
2022-07-23 00:06:32 +02:00
printf ( " Unable to Launch JVM \n " ) ;
2024-05-08 20:03:58 +02:00
} else {
2022-07-23 00:06:32 +02:00
printf ( " JVM launched successfully \n " ) ;
}
2024-05-08 20:03:58 +02:00
free ( options ) ;
2022-07-23 00:06:32 +02:00
return env ;
}
2024-09-15 00:42:55 +02:00
void icon_override ( GtkWidget * window , GList * icon_list )
{
2024-03-11 15:15:21 +01:00
GdkSurface * window_surface = gtk_native_get_surface ( GTK_NATIVE ( window ) ) ;
2024-05-07 22:25:32 +02:00
// set app icon as window icon; this is a noop on Wayland because there is currently no way to set a window icon on Wayland
2024-03-11 15:15:21 +01:00
gdk_toplevel_set_icon_list ( GDK_TOPLEVEL ( window_surface ) , icon_list ) ;
}
/*
* There is no way to get a nice clean callback for when the window is ready to be used for stuff
* that requires non - zero dimensions , so we just check periodically
*/
2022-11-24 16:09:42 +01:00
gboolean hacky_on_window_focus_changed_callback ( JNIEnv * env )
{
2024-09-15 00:42:55 +02:00
if ( gtk_widget_get_width ( window ) ! = 0 ) {
2023-08-11 18:09:17 +02:00
activity_window_ready ( ) ;
2022-11-24 16:09:42 +01:00
return G_SOURCE_REMOVE ;
}
return G_SOURCE_CONTINUE ;
}
2024-04-13 16:34:21 +02:00
struct dynamic_launcher_callback_data { char * desktop_file_id ; char * desktop_entry ; } ;
2024-09-15 00:42:55 +02:00
static void dynamic_launcher_ready_callback ( GObject * portal , GAsyncResult * res , gpointer user_data )
{
2024-04-13 16:34:21 +02:00
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 ) ;
}
2022-10-12 17:23:19 +02:00
// this is exported by the shim bionic linker
void dl_parse_library_path ( const char * path , char * delim ) ;
2022-10-02 23:06:56 +02:00
2024-09-15 00:42:55 +02:00
# define REL_DEX_INSTALL_PATH " / .. / java / dex"
2022-10-12 17:23:19 +02:00
2024-09-15 00:42:55 +02:00
# define REL_API_IMPL_JAR_INSTALL_PATH " / android_translation_layer / api-impl.jar"
2022-10-12 17:23:19 +02:00
# define REL_API_IMPL_NATIVES_INSTALL_PATH " / android_translation_layer / natives"
2024-09-15 00:42:55 +02:00
# define REL_MICROG_APK_INSTALL_PATH " / microg / com.google.android.gms.apk"
# define REL_FRAMEWORK_RES_INSTALL_PATH " / android_translation_layer / framework-res.apk"
2022-10-02 23:06:56 +02:00
2024-09-15 00:42:55 +02:00
# define API_IMPL_JAR_PATH_LOCAL ". / api-impl.jar"
# define MICROG_APK_PATH_LOCAL ". / com.google.android.gms.apk"
# define FRAMEWORK_RES_PATH_LOCAL ". / res / framework-res.apk"
2022-10-02 23:06:56 +02:00
2024-05-08 20:03:58 +02:00
struct jni_callback_data {
char * apk_main_activity_class ;
uint32_t window_width ;
uint32_t window_height ;
gboolean install ;
char * prgname ;
char * * extra_jvm_options ;
2024-05-22 12:24:11 +02:00
char * * extra_string_keys ;
2024-05-08 20:03:58 +02:00
} ;
2024-10-05 22:12:14 +02:00
static char * uri_option = NULL ;
2024-09-15 00:42:55 +02:00
static void open ( GtkApplication * app , GFile * * files , gint nfiles , const gchar * hint , struct jni_callback_data * d )
2022-07-23 00:06:32 +02:00
{
2024-09-15 00:42:55 +02:00
// TODO: pass all files to classpath
2022-07-23 00:06:32 +02:00
/*
printf ( " nfiles: %d \n " , nfiles ) ;
for ( int i = 0 ; i < nfiles ; i + + ) {
printf ( " >- [%s] \n " , g_file_get_path ( files [ i ] ) ) ;
}
*/
2022-10-12 17:23:19 +02:00
char * dex_install_dir ;
2022-10-02 23:06:56 +02:00
char * api_impl_jar ;
2023-06-07 13:25:36 +02:00
char * microg_apk = NULL ;
2023-07-14 20:37:10 +02:00
char * framework_res_apk = NULL ;
2023-08-12 13:05:34 +02:00
const char * package_name ;
2022-10-02 23:06:56 +02:00
int errno_libdir ;
int errno_localdir ;
int ret ;
2023-08-11 18:09:17 +02:00
jobject activity_object ;
2023-08-17 10:09:07 +02:00
jobject application_object ;
2022-10-02 23:06:56 +02:00
2024-09-15 00:42:55 +02:00
char * apk_classpath = g_file_get_path ( files [ 0 ] ) ;
2022-10-12 17:23:19 +02:00
char * apk_name = g_file_get_basename ( files [ 0 ] ) ;
2022-07-23 00:06:32 +02:00
2024-09-15 00:42:55 +02:00
if ( apk_classpath = = NULL ) {
2022-07-23 00:06:32 +02:00
printf ( " error: the specified file path doesn't seem to be valid \n " ) ;
exit ( 1 ) ;
}
2024-09-15 00:42:55 +02:00
if ( access ( apk_classpath , F_OK ) < 0 ) {
2024-01-23 22:32:09 +01:00
printf ( " error: the specified file path doesn't seem to exist (%m) \n " ) ;
2023-12-07 14:56:04 +01:00
exit ( 1 ) ;
}
2022-10-12 17:23:19 +02:00
Dl_info libart_so_dl_info ;
// JNI_CreateJavaVM chosen arbitrarily, what matters is that it's a symbol exported by by libart.so
// TODO: we shouldn't necessarily count on art being installed in the same prefix as we are
dladdr ( JNI_CreateJavaVM , & libart_so_dl_info ) ;
// make sure we didn't get NULL
2024-09-15 00:42:55 +02:00
if ( libart_so_dl_info . dli_fname ) {
2022-10-12 17:23:19 +02:00
// it's simpler if we can modify the string, so strdup it
char * libart_so_full_path = strdup ( libart_so_dl_info . dli_fname ) ;
* strrchr ( libart_so_full_path , ' / ' ) = ' \0 ' ; // now we should have something like /usr/lib64/art
dex_install_dir = malloc ( strlen ( libart_so_full_path ) + strlen ( REL_DEX_INSTALL_PATH ) + 1 ) ; // +1 for NULL
strcpy ( dex_install_dir , libart_so_full_path ) ;
strcat ( dex_install_dir , REL_DEX_INSTALL_PATH ) ;
free ( libart_so_full_path ) ;
} else {
dex_install_dir = " DIDN'T_GET_SO_PATH_WITH_dladdr_SUS " ; // in case we print this as part of some other error, it should be clear what the real cause is
}
2024-09-15 00:42:55 +02:00
char * app_data_dir_base = getenv ( " ANDROID_APP_DATA_DIR " ) ;
if ( ! app_data_dir_base ) {
const char * user_data_dir = g_get_user_data_dir ( ) ;
if ( user_data_dir ) {
2024-04-11 21:16:50 +02:00
app_data_dir_base = g_strdup_printf ( " %s/android_translation_layer " , user_data_dir ) ;
2022-10-12 17:23:19 +02:00
ret = mkdir ( app_data_dir_base , DEFFILEMODE | S_IXUSR | S_IXGRP | S_IXOTH ) ;
if ( ret ) {
2024-09-15 00:42:55 +02:00
if ( errno ! = EEXIST ) {
2022-10-12 17:23:19 +02:00
fprintf ( stderr , " error: ANDROID_APP_DATA_DIR not set, and the default directory (%s) coudldn't be created (error: %s) \n " , app_data_dir_base , strerror ( errno ) ) ;
exit ( 1 ) ;
}
}
} else {
fprintf ( stderr , " error: ANDROID_APP_DATA_DIR not set, and HOME is not set either so we can't construct the default path \n " ) ;
exit ( 1 ) ;
}
}
2023-10-06 21:17:11 +02:00
app_data_dir = malloc ( strlen ( app_data_dir_base ) + 1 + strlen ( apk_name ) + 1 + 1 + 1 ) ; // +1 for middle '/', + 1 for _, +1 for end '/', and +1 for NULL
2022-10-12 17:23:19 +02:00
strcpy ( app_data_dir , app_data_dir_base ) ;
strcat ( app_data_dir , " / " ) ;
2023-10-06 21:17:11 +02:00
// TODO: we should possibly use the app id instead, but we don't currently have a way to get that soon enough
2022-10-12 17:23:19 +02:00
// arguably both the app id and the apk name might have an issue with duplicates, but if two apks use the same app id, chances are it's less of an issue than when two apks have the same name
strcat ( app_data_dir , apk_name ) ;
2023-10-06 21:17:11 +02:00
strcat ( app_data_dir , " _ " ) ; // !IMPORTANT! Unity can't comprehend that a directory name could end in .apk, so we have to avoid that here
2022-10-12 17:23:19 +02:00
strcat ( app_data_dir , " / " ) ;
2023-06-06 22:59:49 +02:00
ret = mkdir ( app_data_dir , DEFFILEMODE | S_IXUSR | S_IXGRP | S_IXOTH ) ;
2024-09-15 00:42:55 +02:00
if ( ret & & errno ! = EEXIST ) {
2023-06-06 22:59:49 +02:00
fprintf ( stderr , " can't create app data dir %s (%s) \n " , app_data_dir , strerror ( errno ) ) ;
2022-10-12 17:23:19 +02:00
exit ( 1 ) ;
}
2022-10-09 23:08:20 +02:00
// check for api-impl.jar and com.google.android.gms.apk in './' first (for running from builddir), and in system install path second
2023-06-06 22:59:49 +02:00
struct stat dont_care ;
2022-10-02 23:06:56 +02:00
ret = stat ( API_IMPL_JAR_PATH_LOCAL , & dont_care ) ;
errno_localdir = errno ;
2024-09-15 00:42:55 +02:00
if ( ! ret ) {
2022-10-12 17:23:19 +02:00
api_impl_jar = strdup ( API_IMPL_JAR_PATH_LOCAL ) ; // for running out of builddir; using strdup so we can always safely call free on this
2022-10-02 23:06:56 +02:00
} else {
2022-10-12 17:23:19 +02:00
char * api_impl_install_dir = malloc ( strlen ( dex_install_dir ) + strlen ( REL_API_IMPL_JAR_INSTALL_PATH ) + 1 ) ; // +1 for NULL
strcpy ( api_impl_install_dir , dex_install_dir ) ;
strcat ( api_impl_install_dir , REL_API_IMPL_JAR_INSTALL_PATH ) ;
ret = stat ( api_impl_install_dir , & dont_care ) ;
2022-10-02 23:06:56 +02:00
errno_libdir = errno ;
2024-09-15 00:42:55 +02:00
if ( ! ret ) {
2022-10-12 17:23:19 +02:00
api_impl_jar = api_impl_install_dir ;
2022-10-02 23:06:56 +02:00
} else {
printf ( " error: can't stat api-impl.jar; tried: \n "
2024-09-15 00:42:55 +02:00
" \t \" " API_IMPL_JAR_PATH_LOCAL " \" , got - %s \n "
" \t \" %s \" , got - %s \n " ,
strerror ( errno_localdir ) ,
2022-10-12 17:23:19 +02:00
api_impl_install_dir , strerror ( errno_libdir ) ) ;
2022-10-02 23:06:56 +02:00
exit ( 1 ) ;
}
}
ret = stat ( MICROG_APK_PATH_LOCAL , & dont_care ) ;
errno_localdir = errno ;
2024-09-15 00:42:55 +02:00
if ( ! ret ) {
2022-10-12 17:23:19 +02:00
microg_apk = strdup ( MICROG_APK_PATH_LOCAL ) ; // for running out of builddir; using strdup so we can always safely call free on this
2022-10-02 23:06:56 +02:00
} else {
2022-10-12 17:23:19 +02:00
char * microg_install_dir = malloc ( strlen ( dex_install_dir ) + strlen ( REL_MICROG_APK_INSTALL_PATH ) + 1 ) ; // +1 for NULL
strcpy ( microg_install_dir , dex_install_dir ) ;
strcat ( microg_install_dir , REL_MICROG_APK_INSTALL_PATH ) ;
ret = stat ( microg_install_dir , & dont_care ) ;
2022-10-02 23:06:56 +02:00
errno_libdir = errno ;
2024-09-15 00:42:55 +02:00
if ( ! ret ) {
2022-10-12 17:23:19 +02:00
microg_apk = microg_install_dir ;
2022-10-02 23:06:56 +02:00
} else {
printf ( " warning: can't stat com.google.android.gms.apk; tried: \n "
2024-09-15 00:42:55 +02:00
" \t \" " MICROG_APK_PATH_LOCAL " \" , got - %s \n "
" \t \" %s \" , got - %s \n " ,
2022-10-12 17:23:19 +02:00
strerror ( errno_localdir ) ,
microg_install_dir , strerror ( errno_libdir ) ) ;
2022-10-02 23:06:56 +02:00
}
}
2023-07-14 20:37:10 +02:00
ret = stat ( FRAMEWORK_RES_PATH_LOCAL , & dont_care ) ;
errno_localdir = errno ;
2024-09-15 00:42:55 +02:00
if ( ! ret ) {
2023-07-14 20:37:10 +02:00
framework_res_apk = strdup ( FRAMEWORK_RES_PATH_LOCAL ) ; // for running out of builddir; using strdup so we can always safely call free on this
} else {
char * framework_res_install_dir = malloc ( strlen ( dex_install_dir ) + strlen ( REL_FRAMEWORK_RES_INSTALL_PATH ) + 1 ) ; // +1 for NULL
strcpy ( framework_res_install_dir , dex_install_dir ) ;
strcat ( framework_res_install_dir , REL_FRAMEWORK_RES_INSTALL_PATH ) ;
ret = stat ( framework_res_install_dir , & dont_care ) ;
errno_libdir = errno ;
2024-09-15 00:42:55 +02:00
if ( ! ret ) {
2023-07-14 20:37:10 +02:00
framework_res_apk = framework_res_install_dir ;
} else {
printf ( " warning: can't stat framework-res.apk; tried: \n "
2024-09-15 00:42:55 +02:00
" \t \" " FRAMEWORK_RES_PATH_LOCAL " \" , got - %s \n "
" \t \" %s \" , got - %s \n " ,
2023-07-14 20:37:10 +02:00
strerror ( errno_localdir ) ,
framework_res_install_dir , strerror ( errno_libdir ) ) ;
}
}
2022-10-12 17:23:19 +02:00
char * api_impl_natives_dir = malloc ( strlen ( dex_install_dir ) + strlen ( REL_API_IMPL_NATIVES_INSTALL_PATH ) + 1 ) ; // +1 for NULL
strcpy ( api_impl_natives_dir , dex_install_dir ) ;
strcat ( api_impl_natives_dir , REL_API_IMPL_NATIVES_INSTALL_PATH ) ;
char * app_lib_dir = malloc ( strlen ( app_data_dir ) + strlen ( " /lib " ) + 1 ) ; // +1 for NULL
strcpy ( app_lib_dir , app_data_dir ) ;
strcat ( app_lib_dir , " /lib " ) ;
2023-06-06 22:08:00 +02:00
// create lib dir
mkdir ( app_lib_dir , DEFFILEMODE | S_IXUSR | S_IXGRP | S_IXOTH ) ;
2022-10-02 23:06:56 +02:00
2024-06-16 08:42:05 +02:00
// Apps which extract libraries on their own can place them anywhere in app_data_dir. Therefore, we add app_data_dir/** to the
// BIONIC_LD_LIBRARY_PATH. While app_data_dir/lib is already matched by the wildcard, it needs to be specified again to allow loading
// libraries by libname from app_data_dir/lib
char * ld_path = g_strdup_printf ( " %s:%s** " , app_lib_dir , app_data_dir ) ;
2022-10-12 17:23:19 +02:00
// calling directly into the shim bionic linker to whitelist the app's lib dir as containing bionic-linked libraries
2024-06-16 08:42:05 +02:00
dl_parse_library_path ( ld_path , " : " ) ;
g_free ( ld_path ) ;
2022-10-12 17:23:19 +02:00
2024-09-15 00:42:55 +02:00
JNIEnv * env = create_vm ( api_impl_jar , apk_classpath , microg_apk , framework_res_apk , api_impl_natives_dir , app_lib_dir , d - > extra_jvm_options ) ;
2022-10-12 17:23:19 +02:00
free ( app_lib_dir ) ;
2022-07-23 00:06:32 +02:00
2023-08-23 10:04:24 +02:00
jclass display_class = ( * env ) - > FindClass ( env , " android/view/Display " ) ;
_SET_STATIC_INT_FIELD ( display_class , " window_width " , d - > window_width ) ;
_SET_STATIC_INT_FIELD ( display_class , " window_height " , d - > window_height ) ;
2023-08-12 13:05:34 +02:00
/* -- register our JNI library under the appropriate classloader -- */
/* 'android/view/View' is part of the "hax.dex" package, any other function from that package would serve just as well */
jmethodID getClassLoader = _METHOD ( ( * env ) - > FindClass ( env , " java/lang/Class " ) , " getClassLoader " , " ()Ljava/lang/ClassLoader; " ) ;
jobject class_loader = ( * env ) - > CallObjectMethod ( env , ( * env ) - > FindClass ( env , " android/view/View " ) , getClassLoader ) ;
jclass java_runtime_class = ( * env ) - > FindClass ( env , " java/lang/Runtime " ) ;
jmethodID getRuntime = _STATIC_METHOD ( java_runtime_class , " getRuntime " , " ()Ljava/lang/Runtime; " ) ;
jobject java_runtime = ( * env ) - > CallStaticObjectMethod ( env , java_runtime_class , getRuntime ) ;
/* this method is private, but it seems we get away with calling it from C */
jmethodID loadLibrary_with_classloader = _METHOD ( java_runtime_class , " loadLibrary " , " (Ljava/lang/String;Ljava/lang/ClassLoader;)V " ) ;
( * env ) - > CallVoidMethod ( env , java_runtime , loadLibrary_with_classloader , _JSTRING ( " translation_layer_main " ) , class_loader ) ;
2024-06-19 20:47:49 +02:00
// some apps need the apk path since they directly read their apk
apk_path = strdup ( apk_classpath ) ;
2024-01-26 12:42:06 +01:00
set_up_handle_cache ( env ) ;
2023-08-12 13:05:34 +02:00
/* -- misc -- */
2022-11-24 16:09:42 +01:00
window = gtk_application_window_new ( app ) ;
2022-07-23 00:06:32 +02:00
2024-09-15 00:42:55 +02:00
if ( getenv ( " ATL_DISABLE_WINDOW_DECORATIONS " ) )
2023-01-09 14:50:17 +01:00
gtk_window_set_decorated ( GTK_WINDOW ( window ) , 0 ) ;
2024-09-15 00:42:55 +02:00
if ( getenv ( " ATL_FORCE_FULLSCREEN " ) )
gtk_window_fullscreen ( GTK_WINDOW ( window ) ) ;
2024-03-21 21:11:26 +01:00
2024-06-15 22:32:01 +02:00
prepare_main_looper ( env ) ;
2023-08-17 10:09:07 +02:00
// construct Application
application_object = ( * env ) - > CallStaticObjectMethod ( env , handle_cache . context . class ,
2024-09-15 00:42:55 +02:00
_STATIC_METHOD ( handle_cache . context . class , " createApplication " , " (J)Landroid/app/Application; " ) , window ) ;
if ( ( * env ) - > ExceptionCheck ( env ) )
2023-08-17 10:09:07 +02:00
( * env ) - > ExceptionDescribe ( env ) ;
2023-06-06 22:08:00 +02:00
/* extract native libraries from apk*/
2024-09-15 00:42:55 +02:00
if ( ! getenv ( " ATL_SKIP_NATIVES_EXTRACTION " ) )
2024-05-07 22:25:32 +02:00
extract_from_apk ( " lib/ " NATIVE_ARCH " / " , " lib/ " ) ;
2023-05-20 18:53:20 +02:00
2023-09-13 21:34:24 +02:00
jclass content_provider = ( * env ) - > FindClass ( env , " android/content/ContentProvider " ) ;
2024-01-23 22:32:09 +01:00
( * env ) - > CallStaticVoidMethod ( env , content_provider , _STATIC_METHOD ( content_provider , " createContentProviders " , " ()V " ) ) ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2023-09-13 21:34:24 +02:00
( * env ) - > ExceptionDescribe ( env ) ;
2023-08-17 10:09:07 +02:00
( * env ) - > CallVoidMethod ( env , application_object , _METHOD ( handle_cache . application . class , " onCreate " , " ()V " ) ) ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2023-08-17 10:09:07 +02:00
( * env ) - > ExceptionDescribe ( env ) ;
2023-08-08 10:16:17 +02:00
// construct main Activity
2023-09-25 19:53:20 +02:00
activity_object = ( * env ) - > CallStaticObjectMethod ( env , handle_cache . activity . class ,
2024-10-05 22:12:14 +02:00
_STATIC_METHOD ( handle_cache . activity . class , " createMainActivity " , " (Ljava/lang/String;JLjava/lang/String;)Landroid/app/Activity; " ) ,
_JSTRING ( d - > apk_main_activity_class ) , _INTPTR ( window ) , uri_option ? _JSTRING ( uri_option ) : NULL ) ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2022-11-08 21:12:41 +01:00
( * env ) - > ExceptionDescribe ( env ) ;
2024-10-05 22:12:14 +02:00
if ( uri_option )
g_free ( uri_option ) ;
2022-11-08 21:12:41 +01:00
2024-09-15 00:42:55 +02:00
if ( d - > extra_string_keys ) {
2024-05-22 12:24:11 +02:00
GError * error = NULL ;
GRegex * regex = g_regex_new ( " (?<! \\ \\ )= " , 0 , 0 , & error ) ;
if ( ! regex ) {
fprintf ( stderr , " g_regex_new error: '%s' \n " , error - > message ) ;
exit ( 1 ) ;
}
jobject intent = _GET_OBJ_FIELD ( activity_object , " intent " , " Landroid/content/Intent; " ) ;
2024-09-15 00:42:55 +02:00
for ( char * * arg = d - > extra_string_keys ; * arg ; arg + + ) {
2024-05-22 12:24:11 +02:00
gchar * * keyval = g_regex_split_full ( regex , * arg , - 1 , 0 , 0 , 2 , NULL ) ;
2024-09-15 00:42:55 +02:00
if ( ! keyval | | ! keyval [ 0 ] | | ! keyval [ 1 ] ) {
2024-05-22 12:24:11 +02:00
fprintf ( stderr , " extra string arg not in 'key=value' format: '%s' \n " , * arg ) ;
exit ( 1 ) ;
}
( * env ) - > CallObjectMethod ( env , intent , handle_cache . intent . putExtraCharSequence , _JSTRING ( keyval [ 0 ] ) , _JSTRING ( keyval [ 1 ] ) ) ;
g_strfreev ( keyval ) ;
}
g_regex_unref ( regex ) ;
g_strfreev ( d - > extra_string_keys ) ;
}
2023-08-12 13:05:34 +02:00
2024-05-22 12:24:11 +02:00
/* -- set the window title and app icon -- */
2023-08-12 13:05:34 +02:00
2024-06-13 20:57:48 +02:00
jstring package_name_jstr = ( * env ) - > CallObjectMethod ( env , activity_object , handle_cache . context . get_package_name ) ;
package_name = package_name_jstr ? _CSTRING ( package_name_jstr ) : NULL ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2023-08-12 13:05:34 +02:00
( * env ) - > ExceptionDescribe ( env ) ;
2024-06-13 20:57:48 +02:00
jstring app_icon_path_jstr = ( * env ) - > CallObjectMethod ( env , application_object , handle_cache . application . get_app_icon_path ) ;
const char * app_icon_path = app_icon_path_jstr ? _CSTRING ( app_icon_path_jstr ) : NULL ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2023-08-12 13:05:34 +02:00
( * env ) - > ExceptionDescribe ( env ) ;
2024-04-13 16:34:21 +02:00
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; " ) ) ) ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2024-04-13 16:34:21 +02:00
( * 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 ) ;
}
2024-04-13 16:56:09 +02:00
GFile * dest = g_file_new_build_filename ( app_data_dir_base , " _installed_apks_ " , apk_name , NULL ) ;
printf ( " installing %s to %s \n " , apk_name , g_file_get_path ( dest ) ) ;
g_file_make_directory ( g_file_get_parent ( dest ) , NULL , NULL ) ;
g_file_copy ( files [ 0 ] , dest , G_FILE_COPY_OVERWRITE , NULL , NULL , NULL , NULL ) ;
2024-04-13 16:34:21 +02:00
2024-10-05 22:12:14 +02:00
jmethodID get_supported_mime_types = _METHOD ( handle_cache . application . class , " get_supported_mime_types " , " ()Ljava/lang/String; " ) ;
jstring supported_mime_types_jstr = ( * env ) - > CallObjectMethod ( env , application_object , get_supported_mime_types ) ;
const char * supported_mime_types = supported_mime_types_jstr ? _CSTRING ( supported_mime_types_jstr ) : NULL ;
if ( ( * env ) - > ExceptionCheck ( env ) )
( * env ) - > ExceptionDescribe ( env ) ;
2024-09-15 00:42:55 +02:00
GString * desktop_entry = g_string_new ( " [Desktop Entry] \n "
" Type=Application \n "
" Exec=env " ) ;
2024-04-13 16:34:21 +02:00
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 ( ) ) ;
}
2024-09-13 20:36:44 +02:00
char * envs [ ] = { " RUN_FROM_BUILDDIR " , " LD_LIBRARY_PATH " , " ANDROID_APP_DATA_DIR " , " ATL_UGLY_ENABLE_LOCATION " , " ATL_UGLY_ENABLE_WEBVIEW " , " ATL_DISABLE_WINDOW_DECORATIONS " , " ATL_FORCE_FULLSCREEN " } ;
2024-09-15 00:42:55 +02:00
for ( int i = 0 ; i < ARRAY_SIZE ( envs ) ; i + + ) {
2024-04-13 16:34:21 +02:00
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 ) ;
2024-10-05 22:12:14 +02:00
g_string_append_printf ( desktop_entry , " %s --uri %%u \n " , g_file_get_path ( dest ) ) ;
if ( supported_mime_types )
g_string_append_printf ( desktop_entry , " MimeType=%s \n " , supported_mime_types ) ;
2024-04-13 16:34:21 +02:00
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 ;
}
2023-08-12 13:05:34 +02:00
gtk_window_set_title ( GTK_WINDOW ( window ) , package_name ) ;
gtk_window_set_default_size ( GTK_WINDOW ( window ) , d - > window_width , d - > window_height ) ;
2024-09-15 00:42:55 +02:00
g_signal_connect ( window , " close-request " , G_CALLBACK ( app_exit ) , env ) ;
2023-08-12 13:05:34 +02:00
2024-04-13 16:34:21 +02:00
gtk_window_present ( GTK_WINDOW ( window ) ) ;
2024-04-13 14:47:55 +02:00
// 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 ) ) {
gdk_wayland_toplevel_set_application_id ( GDK_WAYLAND_TOPLEVEL ( toplevel ) , package_name ) ;
}
2024-09-15 00:42:55 +02:00
if ( app_icon_path ) {
char * app_icon_path_full = malloc ( strlen ( app_data_dir ) + 1 + strlen ( app_icon_path ) + 1 ) ; // +1 for /, +1 for NULL
2024-03-11 15:15:21 +01:00
sprintf ( app_icon_path_full , " %s/%s " , app_data_dir , app_icon_path ) ;
2023-08-12 13:05:34 +02:00
2024-03-11 15:15:21 +01:00
extract_from_apk ( app_icon_path , app_icon_path ) ;
2023-08-12 13:05:34 +02:00
2024-03-11 15:15:21 +01:00
GError * error = NULL ;
GList * icon_list = g_list_append ( NULL , gdk_texture_new_from_filename ( app_icon_path_full , & error ) ) ;
2024-09-15 00:42:55 +02:00
if ( error ) {
2024-03-11 15:15:21 +01:00
printf ( " gdk_texture_new_from_filename: %s \n " , error - > message ) ;
g_clear_error ( & error ) ;
}
icon_override ( window , icon_list ) ;
/* if Gtk sets the icon list to NULL, override it again */
2024-09-15 00:42:55 +02:00
g_signal_connect_after ( window , " realize " , G_CALLBACK ( icon_override ) , icon_list ) ;
2024-03-11 15:15:21 +01:00
}
2023-08-12 13:05:34 +02:00
2023-08-11 18:09:17 +02:00
activity_start ( env , activity_object ) ;
2022-10-26 18:39:04 +02:00
2022-11-24 16:09:42 +01:00
g_timeout_add ( 10 , G_SOURCE_FUNC ( hacky_on_window_focus_changed_callback ) , env ) ;
2022-11-11 19:18:21 +01:00
jobject input_queue_callback = g_object_get_data ( G_OBJECT ( window ) , " input_queue_callback " ) ;
2024-09-15 00:42:55 +02:00
if ( input_queue_callback ) {
2022-11-11 19:18:21 +01:00
jobject input_queue = g_object_get_data ( G_OBJECT ( window ) , " input_queue " ) ;
( * env ) - > CallVoidMethod ( env , input_queue_callback , handle_cache . input_queue_callback . onInputQueueCreated , input_queue ) ;
2024-09-15 00:42:55 +02:00
if ( ( * env ) - > ExceptionCheck ( env ) )
2022-11-11 19:18:21 +01:00
( * env ) - > ExceptionDescribe ( env ) ;
}
2022-07-23 00:06:32 +02:00
}
static void activate ( GtkApplication * app , struct jni_callback_data * d )
{
2024-09-15 00:42:55 +02:00
printf ( " error: usage: ./android-translation-layer [app.apk] [-l path/to/activity] \n "
2024-03-11 15:15:21 +01:00
" you can specify --help to see the list of options \n " ) ;
2022-07-23 00:06:32 +02:00
exit ( 1 ) ;
}
2024-10-05 22:12:14 +02:00
static gboolean option_uri_cb ( const gchar * option_name , const gchar * value , gpointer data , GError * * error )
{
printf ( " option_uri_cb: %s %s, %p, %p \n " , option_name , value , data , error ) ;
uri_option = g_strdup ( value ) ;
return TRUE ;
}
2022-07-23 00:06:32 +02:00
void init_cmd_parameters ( GApplication * app , struct jni_callback_data * d )
{
2024-09-15 00:42:55 +02:00
const GOptionEntry cmd_params [ ] = {
/* long_name | short_name | flags | arg | arg_data | description | arg_desc */
{ " launch-activity " , ' l ' , 0 , G_OPTION_ARG_STRING , & d - > apk_main_activity_class , " the fully qualifed name of the activity you wish to launch (usually the apk's main activity) " , " ACTIVITY_NAME " } ,
{ " window-width " , ' w ' , 0 , G_OPTION_ARG_INT , & d - > window_width , " window width to launch with (some apps react poorly to runtime window size adjustments) " , " WIDTH " } ,
{ " window-height " , ' h ' , 0 , G_OPTION_ARG_INT , & d - > window_height , " window height to launch with (some apps react poorly to runtime window size adjustments) " , " HEIGHT " } ,
{ " install " , ' i ' , 0 , G_OPTION_ARG_NONE , & d - > install , " install .desktop file for the given apk " , NULL } ,
{ " extra-jvm-option " , ' X ' , 0 , G_OPTION_ARG_STRING_ARRAY , & d - > extra_jvm_options , " pass an additional option directly to art (e.g -X \" -verbose:jni \" ) " , " \" OPTION \" " } ,
{ " extra-string-key " , ' e ' , 0 , G_OPTION_ARG_STRING_ARRAY , & d - > extra_string_keys , " pass a string extra (-e key=value) " , " \" KEY=VALUE \" " } ,
2024-10-05 22:12:14 +02:00
{ " uri " , ' u ' , G_OPTION_FLAG_OPTIONAL_ARG , G_OPTION_ARG_CALLBACK , option_uri_cb , " open the given URI inside the application " , " URI " } ,
2024-09-15 00:42:55 +02:00
{ NULL }
} ;
g_application_add_main_option_entries ( G_APPLICATION ( app ) , cmd_params ) ;
2022-07-23 00:06:32 +02:00
}
2022-10-02 23:06:56 +02:00
void init__r_debug ( ) ;
2024-03-18 15:41:44 +01:00
void remove_ongoing_notifications ( ) ;
2022-10-02 23:06:56 +02:00
2024-03-11 15:15:21 +01:00
int main ( int argc , char * * argv )
2022-07-23 00:06:32 +02:00
{
2023-08-12 13:05:34 +02:00
GtkApplication * app ;
int status ;
2022-07-23 00:06:32 +02:00
2022-10-02 23:06:56 +02:00
/* this has to be done in the main executable, so might as well do it here*/
2022-09-20 15:32:37 +02:00
init__r_debug ( ) ;
2024-06-19 18:04:05 +02:00
// locale on android is always either C or C.UTF-8, and some apps might unbeknownst to them depend on that
2023-01-05 18:58:52 +01:00
// for correct functionality
2024-06-19 18:04:05 +02:00
setenv ( " LC_ALL " , " C.UTF-8 " , 1 ) ;
2023-01-05 18:58:52 +01:00
2022-07-23 00:06:32 +02:00
struct jni_callback_data * callback_data = malloc ( sizeof ( struct jni_callback_data ) ) ;
callback_data - > apk_main_activity_class = NULL ;
callback_data - > window_width = 960 ;
callback_data - > window_height = 540 ;
2024-04-13 16:34:21 +02:00
callback_data - > install = FALSE ;
callback_data - > prgname = argv [ 0 ] ;
2024-05-08 20:03:58 +02:00
callback_data - > extra_jvm_options = NULL ;
2024-05-22 12:24:11 +02:00
callback_data - > extra_string_keys = NULL ;
2022-07-23 00:06:32 +02:00
2024-04-13 16:34:21 +02:00
app = gtk_application_new ( " com.example.demo_application " , G_APPLICATION_NON_UNIQUE | G_APPLICATION_HANDLES_OPEN | G_APPLICATION_CAN_OVERRIDE_APP_ID ) ;
2022-07-23 00:06:32 +02:00
// cmdline related setup
init_cmd_parameters ( G_APPLICATION ( app ) , callback_data ) ;
2023-01-12 13:25:32 +01:00
g_application_set_option_context_summary ( G_APPLICATION ( app ) , " a translation layer for running android applications natively on Linux " ) ;
2022-07-23 00:06:32 +02:00
2024-09-15 00:42:55 +02:00
g_signal_connect ( app , " activate " , G_CALLBACK ( activate ) , callback_data ) ;
g_signal_connect ( app , " open " , G_CALLBACK ( open ) , callback_data ) ;
2023-08-12 13:05:34 +02:00
status = g_application_run ( G_APPLICATION ( app ) , argc , argv ) ;
g_object_unref ( app ) ;
2024-03-18 15:41:44 +01:00
remove_ongoing_notifications ( ) ;
2022-07-23 00:06:32 +02:00
2023-08-12 13:05:34 +02:00
return status ;
2022-07-23 00:06:32 +02:00
}
2023-08-12 13:05:34 +02:00
/* TODO: recall what this is doing here */
const char dl_loader [ ] __attribute__ ( ( section ( " .interp " ) ) ) = " /lib/ld-linux.so.2 " ;