#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gfx.h" static TTF_Font* sFont; static int unpackFinished = 0; enum { UPGRADE_WARNING_NONE, UPGRADE_WARNING_ASK, UPGRADE_WARNING_PROCEED, UPGRADE_WARNING_CANCEL }; // Mutex here would be nice, but I'm lazy and don't care static int upgradeWarning = UPGRADE_WARNING_NONE; static char unpackLog[4][256]; static int freeSpaceRequiredMb = 0; static void renderString(const char *c, int x, int y); static void renderStringColor(const char *c, int x, int y, int r, int g, int b, SDL_Surface * surf); static void renderStringScaled(const char *c, int size, int x, int y, int r, int g, int b, SDL_Surface * surf); static void * unpackFilesThread(void * unused); static void showErrorMessage(const char *msg); static int unpackFiles(const char *archive, const char *script, const char *deleteOldDataMarkerFile, const char *pathsToDelete) { int unpackProgressMb; int unpackProgressMbTotal = 1; char fname[PATH_MAX*3]; char fname2[PATH_MAX*2]; char buf[1024 * 4]; struct stat st; const char *tarExtractCommand = "tar xz -C"; char tarxz[PATH_MAX]; if( stat( archive, &st ) == 0 ) { unpackProgressMbTotal = st.st_size / 1024 / 1024; if( unpackProgressMbTotal <= 0 ) unpackProgressMbTotal = 1; __android_log_print(ANDROID_LOG_INFO, "XSDL", "Unpacking data: total size %d Mb", unpackProgressMbTotal); } else { if (strstr(archive, ".tar.gz") == archive + strlen(archive) - strlen(".tar.gz")) { strcpy(tarxz, archive); strstr(tarxz, ".tar.gz")[5] = 'x'; // .tar.gz -> .tar.xz if( stat( tarxz, &st ) == 0 ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Found .tar.xz archive: %s", tarxz); archive = tarxz; tarExtractCommand = "tar xJ -C"; unpackProgressMbTotal = st.st_size / 1024 / 1024; if( unpackProgressMbTotal <= 0 ) unpackProgressMbTotal = 1; __android_log_print(ANDROID_LOG_INFO, "XSDL", "Unpacking data: total size %d Mb", unpackProgressMbTotal); } else return 1; } } unpackProgressMb = 0; strcpy( fname2, getenv("SECURE_STORAGE_DIR") ); strcat( fname2, "/" ); strcat( fname2, deleteOldDataMarkerFile ); if( strlen(deleteOldDataMarkerFile) > 0 && stat( fname2, &st ) == 0 ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Upgrade detected, showing warning dialog"); upgradeWarning = UPGRADE_WARNING_ASK; while( upgradeWarning == UPGRADE_WARNING_ASK ) SDL_Delay(200); if( upgradeWarning == UPGRADE_WARNING_CANCEL ) return 1; __android_log_print(ANDROID_LOG_INFO, "XSDL", "Deleting old installation..."); sprintf(unpackLog[0], "Deleting old installation..."); strcpy( fname, getenv("SECURE_STORAGE_DIR") ); strcat( fname, "/busybox" ); strcat( fname, " sh -c 'cd " ); strcat( fname, getenv("SECURE_STORAGE_DIR") ); strcat( fname, " ; rm -rf " ); strcat( fname, pathsToDelete ); strcat( fname, "'" ); __android_log_print(ANDROID_LOG_INFO, "XSDL", "%s", fname); system( fname ); } for (;;) { struct statfs freeSpace; memset(&freeSpace, 0, sizeof(freeSpace)); if( statfs(getenv("SECURE_STORAGE_DIR"), &freeSpace) == 0 ) { if( (uint64_t)freeSpace.f_bsize * (uint64_t)freeSpace.f_bavail < (uint64_t)freeSpaceRequiredMb * 1024 * 1024 ) { sprintf(unpackLog[0], "Error: not enough free space on internal storage"); sprintf(unpackLog[1], "Available %llu Mb, required %d Mb", (uint64_t)freeSpace.f_bsize * freeSpace.f_bavail / 1024 / 1024, freeSpaceRequiredMb); sprintf(unpackLog[2], "Uninstall large apps to free more space on internal storage"); sleep(1); continue; } } sprintf(unpackLog[1], " "); sprintf(unpackLog[2], " "); break; } sprintf(unpackLog[0], "Unpacking data: %s", archive); __android_log_print(ANDROID_LOG_INFO, "XSDL", "Unpacking data: %s", archive); strcpy( fname, getenv("SECURE_STORAGE_DIR") ); strcat( fname, "/busybox" ); strcat( fname, " " ); strcat( fname, tarExtractCommand ); strcat( fname, " " ); strcat( fname, getenv("SECURE_STORAGE_DIR") ); FILE * fo = popen(fname, "w"); FILE * ff = fopen(archive, "rb"); if( !ff || !fo ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Error extracting data"); sprintf(unpackLog[0], "Error extracting data"); return 0; } __android_log_print(ANDROID_LOG_INFO, "XSDL", "POPEN OK"); int unpackProgressKb = 0; for(;;) { //__android_log_print(ANDROID_LOG_INFO, "XSDL", "FREAD %d", unpackProgressKb); int cnt = fread( buf, 1, sizeof(buf), ff ); //__android_log_print(ANDROID_LOG_INFO, "XSDL", "FREAD %d READ %d", unpackProgressKb, cnt); if( cnt < 0 ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Error extracting data"); sprintf(unpackLog[0], "Error extracting data"); return 1; } fwrite( buf, 1, cnt, fo ); if( cnt < sizeof(buf) ) break; unpackProgressKb += 4; if( unpackProgressKb >= 1024 ) { unpackProgressKb = 0; unpackProgressMb++; sprintf(unpackLog[0], "Unpacking data: %d/%d Mb, %d%%", unpackProgressMb, unpackProgressMbTotal, unpackProgressMb * 100 / (unpackProgressMbTotal > 0 ? unpackProgressMbTotal : 1)); } } __android_log_print(ANDROID_LOG_INFO, "XSDL", "FREAD %d Mb DONE", unpackProgressMb); fclose(ff); if( pclose(fo) != 0 ) // Returns error on Android 2.3 emulator! { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Error extracting data - pclose() returned error, ignoring that error"); //return 0; } __android_log_print(ANDROID_LOG_INFO, "XSDL", "Extracting data finished"); remove(archive); if( strlen(script) == 0 ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "No postinstall script"); return 1; } sprintf(unpackLog[0], "Running postinstall script: %s", script); strcpy( fname, getenv("SECURE_STORAGE_DIR") ); strcat( fname, "/" ); strcat( fname, script ); if( stat( fname, &st ) != 0 ) { strcpy( fname2, getenv("UNSECURE_STORAGE_DIR") ); strcat( fname2, "/" ); strcat( fname2, script ); if( stat( fname2, &st ) != 0 ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Cannot find postinstall script"); return 1; } else { strcpy( fname2, "cat " ); strcat( fname2, getenv("UNSECURE_STORAGE_DIR") ); strcat( fname2, "/" ); strcat( fname2, script ); strcat( fname2, " > " ); strcat( fname2, fname ); __android_log_print(ANDROID_LOG_INFO, "XSDL", "Copying postinstall script from SD card: %s", fname2); system( fname2 ); } } __android_log_print(ANDROID_LOG_INFO, "XSDL", "Setting executable permissions on postinstall script"); strcpy( fname2, "chmod 755 " ); strcat( fname2, fname ); system( fname2 ); __android_log_print(ANDROID_LOG_INFO, "XSDL", "Running postinstall scipt: %s", script); fo = popen(fname, "r"); if( !fo ) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "ERROR: Cannot launch postinstall script"); return 0; } for(;;) { if( !fgets(buf, sizeof(buf), fo) ) break; if( strchr(buf, '\n') != NULL ) strchr(buf, '\n')[0] = 0; __android_log_print(ANDROID_LOG_INFO, "XSDL", "> %s", buf); strncpy(unpackLog[3], unpackLog[2], sizeof(unpackLog[0]) - 4); strncpy(unpackLog[2], unpackLog[1], sizeof(unpackLog[0]) - 4); strncpy(unpackLog[1], unpackLog[0], sizeof(unpackLog[0]) - 4); strncpy(unpackLog[0], buf, sizeof(unpackLog[0]) - 4); } __android_log_print(ANDROID_LOG_INFO, "XSDL", "Postinstall script exited with status %d", pclose(fo)); sprintf(unpackLog[0], "Running postinstall script finished"); return 1; } static void * unpackFilesThread(void * unused) { const char *unpack[][4] = { { "data.tar.gz", "postinstall.sh", "usr/lib/xorg/protocol.txt", "img img-* postinstall.sh update*.sh" }, { "xfonts.tar.gz", "", "", "" }, { "update1.tar.gz", "update1.sh", "", "" }, { "update2.tar.gz", "update2.sh", "", "" }, { "update3.tar.gz", "update3.sh", "", "" }, { "update4.tar.gz", "update4.sh", "", "" }, { "update5.tar.gz", "update5.sh", "", "" }, { "update6.tar.gz", "update6.sh", "", "" }, { "update7.tar.gz", "update7.sh", "", "" }, { "update8.tar.gz", "update8.sh", "", "" }, { "update9.tar.gz", "update9.sh", "", "" }, {NULL, NULL, NULL, NULL} }; int i; for( i = 0; unpack[i][0] != NULL; i++ ) { int status = unpackFiles(unpack[i][0], unpack[i][1], unpack[i][2], unpack[i][3]); if( status == 0 && i == 0 ) // Only the first archive is mandatory { unpackFinished = 1; return (void *)0; } } unpackFinished = 1; return (void *)1; } void XSDL_unpackFiles(int _freeSpaceRequiredMb) { pthread_t thread_id; void * status; memset(unpackLog, 0, sizeof(unpackLog)); freeSpaceRequiredMb = _freeSpaceRequiredMb; pthread_create(&thread_id, NULL, &unpackFilesThread, NULL); int progress = 0; enum {PROGRESS_WHEEL_NUM = 14}; const char *progressWheel[PROGRESS_WHEEL_NUM] = { "[<=>=======]", "[=<=>======]", "[==<=>=====]", "[===<=>====]", "[====<=>===]", "[=====<=>==]", "[======<=>=]", "[=======<=>]", "[======<=>=]", "[=====<=>==]", "[====<=>===]", "[===<=>====]", "[==<=>=====]", "[=<=>======]", }; /* const char *progressWheel[PROGRESS_WHEEL_NUM] = { "°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´", "´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚", "˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´", "´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°", "°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤", "¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø", "ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,", ",ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸", "¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳", "˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸", "¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,", ",¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø", "ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤", "¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°¤ø,¸˳¸,ø¤°´˚´°", }; */ SDL_Joystick * j0 = SDL_JoystickOpen(0); while (!unpackFinished) { SDL_Delay(300); SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); renderString(unpackLog[0], VID_X/2, VID_Y*2/8); renderString(unpackLog[1], VID_X/2, VID_Y*3/8); renderString(unpackLog[2], VID_X/2, VID_Y*4/8); renderString(unpackLog[3], VID_X/2, VID_Y*5/8); progress++; renderString(progressWheel[progress % PROGRESS_WHEEL_NUM], VID_X/2, VID_Y*6/8); renderString("You may put this app to background while it's unpacking", VID_X/2, VID_Y*7/8); SDL_Flip(SDL_GetVideoSurface()); int x = 0, y = 0; while( upgradeWarning == UPGRADE_WARNING_ASK ) { SDL_Delay(100); SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); char s[PATH_MAX]; sprintf(s, "New update available for %s", getenv("ANDROID_APP_NAME")); renderString(s, VID_X/2, VID_Y*2/8); sprintf(s, "Please move all your %s files to SD card", getenv("ANDROID_APP_NAME")); renderString(s, VID_X/2, VID_Y*3/8); renderString("or they will be deleted during upgrade", VID_X/2, VID_Y*4/8); renderString("――――――――――――――――――――", VID_X/4, VID_Y*11/16); renderString("| |", VID_X/4, VID_Y*45/64); renderString("| |", VID_X/4, VID_Y*48/64); renderString("| |", VID_X/4, VID_Y*101/128); renderString("――――――――――――――――――――", VID_X/4, VID_Y*13/16); renderString("Install now", VID_X/4, VID_Y*6/8); renderString("――――――――――――――――――――", VID_X*3/4, VID_Y*11/16); renderString("| |", VID_X*3/4, VID_Y*45/64); renderString("| |", VID_X*3/4, VID_Y*48/64); renderString("| |", VID_X*3/4, VID_Y*101/128); renderString("――――――――――――――――――――", VID_X*3/4, VID_Y*13/16); renderString("Install later", VID_X*3/4, VID_Y*6/8); SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_HELP) upgradeWarning = UPGRADE_WARNING_CANCEL; break; case SDL_MOUSEBUTTONUP: if( y > VID_Y*5/8 && y < VID_Y*7/8 ) upgradeWarning = (x > VID_X/2) ? UPGRADE_WARNING_CANCEL : UPGRADE_WARNING_PROCEED; break; case SDL_JOYBALLMOTION: x = event.jball.xrel; y = event.jball.yrel; break; } } SDL_Flip(SDL_GetVideoSurface()); } } pthread_join(thread_id, &status); if( status == 0 ) { showErrorMessage("Cannot unpack data files, please reinstall the app"); exit(1); } SDL_JoystickClose(j0); } void XSDL_showConfigMenu(int * resolutionW, int * displayW, int * resolutionH, int * displayH, int * builtinKeyboard, int * ctrlAltShiftKeys) { int x = 0, y = 0, i, ii; SDL_Event event; int res = -1, dpi = -1; int customX = 1000, customY = 1000; enum { MODE_CUSTOM = 11 }; char native[32] = "0x0", native56[32], native46[32], native36[32], native26[32]; char custom[32] = "1000x1000"; int vertical = SDL_ListModes(NULL, 0)[0]->w < SDL_ListModes(NULL, 0)[0]->h; char cfgpath[PATH_MAX]; FILE * cfgfile; int okay = 0; if( vertical ) { x = *resolutionW; *resolutionW = *resolutionH; *resolutionH = x; x = *displayW; *displayW = *displayH; *displayH = x; } const char * resStr[] = { native, native56, native46, native36, native26, "1280x1024", "1280x960", "1280x720", "1024x768", "800x600", "800x480", custom }; const int resVal[][2] = { {*resolutionW, *resolutionH}, {(*resolutionW * 5 / 6) & ~0x3, (*resolutionH * 5 / 6) & ~0x3}, {(*resolutionW * 4 / 6) & ~0x3, (*resolutionH * 4 / 6) & ~0x3}, {(*resolutionW * 3 / 6) & ~0x3, (*resolutionH * 3 / 6) & ~0x3}, {(*resolutionW * 2 / 6) & ~0x3, (*resolutionH * 2 / 6) & ~0x3}, {1280,1024}, {1280,960}, {1280,720}, {1024,768}, {800,600}, {800,480}, {customX,customY} }; const char * fontsStr[] = { "X2.5", "X2", "X1.7", "X1.5", "X1.3", "X1", "X0.9", "X0.8", "X0.7", "X0.6", "X0.5", "X0.4", "X0.3", "X0.2", "X0.15", "X0.1" }; const float fontsVal[] = { 2.5f, 2.0f, 1.7f, 1.5f, 1.3f, 1.0f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.3f, 0.2f, 0.15f, 0.1f }; sprintf(native, "%dx%d", resVal[0][0], resVal[0][1]); sprintf(native56, "%dx%d", resVal[1][0], resVal[1][1]); sprintf(native46, "%dx%d", resVal[2][0], resVal[2][1]); sprintf(native36, "%dx%d", resVal[3][0], resVal[3][1]); sprintf(native26, "%dx%d", resVal[4][0], resVal[4][1]); int savedRes = 0; int savedDpi = 8; sprintf(cfgpath, "%s/.xsdl.cfg", getenv("SECURE_STORAGE_DIR")); cfgfile = fopen(cfgpath, "r"); if( cfgfile ) { fscanf(cfgfile, "%d %d %d %d %d %d", &savedRes, &savedDpi, &customX, &customY, builtinKeyboard, ctrlAltShiftKeys); fclose(cfgfile); } sprintf(custom, "%dx%d", customX, customY); int counter = 3000, config = 0; Uint32 curtime = SDL_GetTicks(); while ( counter > 0 && !config ) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_HELP) counter = 0; break; case SDL_MOUSEBUTTONUP: config = 1; break; } } SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); y = VID_Y/4; renderString("Tap the screen to change", vertical ? VID_Y / 2 : VID_X/2, y); y += 30; renderString("display resolution and font scale (DPI)", vertical ? VID_Y / 2 : VID_X/2, y); char buf[100]; y += 30; sprintf(buf, "Resolution: %s", resStr[savedRes]); renderString(buf, vertical ? VID_Y / 2 : VID_X/2, y); y += 30; sprintf(buf, "Font scale: %s", fontsStr[savedDpi]); renderString(buf, vertical ? VID_Y / 2 : VID_X/2, y); y += 30; sprintf(buf, "Keyboard: %s", *builtinKeyboard == 0 ? "System" : *builtinKeyboard == 1 ? "Builtin QWERTY" : "System + Builtin"); renderString(buf, vertical ? VID_Y / 2 : VID_X/2, y); y += 40; sprintf(buf, "Starting in %d seconds", counter / 1000 + 1); renderString(buf, vertical ? VID_Y / 2 : VID_X/2, y); SDL_Delay(100); SDL_Flip(SDL_GetVideoSurface()); counter -= SDL_GetTicks() - curtime; curtime = SDL_GetTicks(); } if( !config ) { res = savedRes; dpi = savedDpi; } SDL_Joystick * j0 = SDL_JoystickOpen(0); while ( res < 0 ) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_HELP) return; break; case SDL_MOUSEBUTTONUP: { //SDL_GetMouseState(&x, &y); if( vertical ) { int z = x; x = y; y = z; } i = (y / (VID_Y/3)); ii = (x / (VID_X/4)); res = i * 4 + ii; __android_log_print(ANDROID_LOG_INFO, "XSDL", "Screen coords %d %d res %d\n", x, y, res); } break; case SDL_JOYBALLMOTION: x = event.jball.xrel; y = event.jball.yrel; break; } } //__android_log_print(ANDROID_LOG_INFO, "XSDL", "Screen coords %d %d\n", x, y, res); SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); renderString("Select display resolution", vertical ? VID_Y / 2 : VID_X/2, VID_Y/3); for(i = 0; i < 3; i++) for(ii = 0; ii < 4; ii++) { if( vertical ) renderString(resStr[i*4+ii], VID_Y/6 + (i*VID_Y/3), VID_X/8 + (ii*VID_X/4)); else renderString(resStr[i*4+ii], VID_X/8 + (ii*VID_X/4), VID_Y/6 + (i*VID_Y/3)); if( i == 0 && ii == 0 && !vertical ) renderString("native", VID_X/8, VID_Y/6 - VID_Y/12); if( i == 2 && ii == 3 && !vertical ) renderString("custom", VID_X/8 + (ii*VID_X/4), VID_Y/6 - VID_Y/12 + (i*VID_Y/3)); } //SDL_GetMouseState(&x, &y); //renderString("X", x, y); SDL_Delay(100); SDL_Flip(SDL_GetVideoSurface()); if (res == MODE_CUSTOM) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Selected custom display resolution"); SDL_ANDROID_ToggleScreenKeyboardWithoutTextInput(); customX = 0; customY = 0; custom[0] = 0; while (customX == 0 || customY == 0) { while (SDL_PollEvent(&event)) { if (event.type == SDL_KEYDOWN) { switch (event.key.keysym.sym) { case SDLK_HELP: return; case SDLK_RETURN: if (customX == 0) customX = atoi(custom); else customY = atoi(custom); custom[0] = 0; break; case SDLK_BACKSPACE: if (strlen(custom) > 0) custom[strlen(custom) - 1] = 0; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': custom[strlen(custom) + 1] = 0; custom[strlen(custom)] = event.key.keysym.sym; break; default: break; } } } SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); if (customX == 0) renderString("Enter width:", VID_X/8, VID_Y/6); else renderString("Enter height:", VID_X/8, VID_Y/6); renderString("Press Enter when done", VID_X*3/4, VID_Y/6); renderString(custom, VID_X/8 + VID_X/4, VID_Y/6); SDL_Delay(100); SDL_Flip(SDL_GetVideoSurface()); } __android_log_print(ANDROID_LOG_INFO, "XSDL", "Selected custom display resolution: %s = %d %d", custom, customX, customY); } } *resolutionW = resVal[res][0]; *resolutionH = resVal[res][1]; if (res == MODE_CUSTOM) { *resolutionW = customX; *resolutionH = customY; } while ( dpi < 0 ) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_HELP) return; break; case SDL_MOUSEBUTTONUP: { //SDL_GetMouseState(&x, &y); if( vertical ) { int z = x; x = y; y = z; } i = (y / (VID_Y/4)); ii = (x / (VID_X/4)); dpi = i * 4 + ii; __android_log_print(ANDROID_LOG_INFO, "XSDL", "Screen coords %d %d dpi %d\n", x, y, res); } break; case SDL_JOYBALLMOTION: x = event.jball.xrel; y = event.jball.yrel; break; } } SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); renderString("Select font scale (DPI)", vertical ? VID_Y / 2 : VID_X/2, VID_Y/2); for(i = 0; i < 4; i++) for(ii = 0; ii < 4; ii++) { int scale = (float)(*displayH) * (float)(*resolutionH) / 3500.0f * fontsVal[i*4+ii]; if( vertical ) renderStringScaled(fontsStr[i*4+ii], scale < VID_Y/12 ? scale : VID_Y/12, VID_Y/8 + (i*VID_Y/4), VID_X/8 + (ii*VID_X/4), 255, 255, 255, SDL_GetVideoSurface()); else renderStringScaled(fontsStr[i*4+ii], scale, VID_X/8 + (ii*VID_X/4), VID_Y/8 + (i*VID_Y/4), 255, 255, 255, SDL_GetVideoSurface()); } //SDL_GetMouseState(&x, &y); //renderString("X", x, y); SDL_Delay(100); SDL_Flip(SDL_GetVideoSurface()); } *displayW = *displayW / fontsVal[dpi]; *displayH = *displayH / fontsVal[dpi]; okay = !config; while ( !okay ) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_HELP) return; break; case SDL_MOUSEBUTTONUP: { //SDL_GetMouseState(&x, &y); if( vertical ) { int z = x; x = y; y = z; } if( y > 0 && y < VID_Y * 2 / 6 ) *builtinKeyboard = (*builtinKeyboard + 1) % 3; if( y > VID_Y * 2 / 6 && y < VID_Y * 4 / 6 ) *ctrlAltShiftKeys = !*ctrlAltShiftKeys; if( y > VID_Y * 4 / 6 && y < VID_Y * 6 / 6 ) okay = 1; __android_log_print(ANDROID_LOG_INFO, "XSDL", "Screen coords %d %d dpi %d\n", x, y, res); } break; case SDL_JOYBALLMOTION: x = event.jball.xrel; y = event.jball.yrel; break; } } SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); char buf[100]; sprintf(buf, "Keyboard: %s", *builtinKeyboard == 0 ? "System" : *builtinKeyboard == 1 ? "Builtin QWERTY" : "System + Builtin"); renderString(buf, VID_X/2, VID_Y * 1 / 6); sprintf(buf, "Separate Ctrl/Alt/Shift keys: %s", *ctrlAltShiftKeys == 0 ? "No" : "Yes"); renderString(buf, VID_X/2, VID_Y * 3 / 6); sprintf(buf, "Okay"); renderString(buf, VID_X/2, VID_Y * 5 / 6); SDL_Delay(100); SDL_Flip(SDL_GetVideoSurface()); } SDL_JoystickClose(j0); if( config ) { cfgfile = fopen(cfgpath, "w"); if( cfgfile ) { fprintf(cfgfile, "%d %d %d %d %d %d\n", res, dpi, customX, customY, *builtinKeyboard, *ctrlAltShiftKeys); fclose(cfgfile); } } } void XSDL_generateBackground(const char * port, int showHelp, int resolutionW, int resolutionH) { int sd, addr, ifc_num, i; struct ifconf ifc; struct ifreq ifr[20]; SDL_Surface * surf; int y = resolutionH * 1 / 6; char msg[128]; if (resolutionH > resolutionW) resolutionH = resolutionW; if( !showHelp ) { surf = SDL_CreateRGBSurface(SDL_SWSURFACE, 16, 16, 24, 0x0000ff, 0x00ff00, 0xff0000, 0); SDL_FillRect(surf, NULL, 0x00002f); SDL_SaveBMP(surf, "background.bmp"); SDL_FreeSurface(surf); return; } surf = SDL_CreateRGBSurface(SDL_SWSURFACE, resolutionW, resolutionH, 24, 0x0000ff, 0x00ff00, 0xff0000, 0); SDL_FillRect(surf, NULL, 0x00002f); renderStringScaled("Launch these commands on your Linux PC:", 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 30 / VID_Y; sd = socket(PF_INET, SOCK_DGRAM, 0); if (sd > 0) { ifc.ifc_len = sizeof(ifr); ifc.ifc_ifcu.ifcu_buf = (caddr_t)ifr; if (ioctl(sd, SIOCGIFCONF, &ifc) == 0) { ifc_num = ifc.ifc_len / sizeof(struct ifreq); __android_log_print(ANDROID_LOG_INFO, "XSDL", "%d network interfaces found", ifc_num); for (i = 0; i < ifc_num; ++i) { int addr = 0; char saddr[32]; if (ifr[i].ifr_addr.sa_family != AF_INET) continue; if (ioctl(sd, SIOCGIFADDR, &ifr[i]) == 0) addr = ((struct sockaddr_in *)(&ifr[i].ifr_addr))->sin_addr.s_addr; if (addr == 0) continue; sprintf (saddr, "%d.%d.%d.%d", (addr & 0xFF), (addr >> 8 & 0xFF), (addr >> 16 & 0xFF), (addr >> 24 & 0xFF)); __android_log_print(ANDROID_LOG_INFO, "XSDL", "interface: %s address: %s\n", ifr[i].ifr_name, saddr); if (strcmp(saddr, "127.0.0.1") == 0) continue; sprintf (msg, "export DISPLAY=%s%s", saddr, port); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 15 / VID_Y; sprintf (msg, "export PULSE_SERVER=tcp:%s:4712", saddr); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 15 / VID_Y; sprintf (msg, "metacity & gimp"); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 20 / VID_Y; } } close(sd); } y += resolutionH * 10 / VID_Y; sprintf (msg, "To tunnel X over SSH, forward port %d", atoi(port+1) + 6000); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 15 / VID_Y; sprintf (msg, "in your SSH client"); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 20 / VID_Y; sprintf (msg, "If you run Linux in chroot on this device, run:"); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); y += resolutionH * 15 / VID_Y; sprintf (msg, "export DISPLAY=:0 PULSE_SERVER=tcp:127.0.0.1:4712"); renderStringScaled(msg, 12 * resolutionH / VID_Y, resolutionW/2, y, 255, 255, 255, surf); SDL_SaveBMP(surf, "background.bmp"); SDL_FreeSurface(surf); } void XSDL_showServerLaunchErrorMessage() { showErrorMessage( "Error: X server failed to launch.\n\n" "This may happen because of SELinux,\n" "or because installation was corrupted.\n" "Either way, this app will not work, which is sad."); } void showErrorMessage(const char *msg) { SDL_Event event; const char * s; int y = VID_Y/3; SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); for( s = msg; s && s[0]; s = strchr(s, '\n'), s += (s ? 1 : 0), y += 30 ) { const char * s1 = strchr(s, '\n'); int len = s1 ? s1 - s : strlen(s); char buf[512]; strncpy(buf, s, len); buf[len] = 0; if( len > 0 ) renderString(buf, VID_X/2, y); } SDL_Flip(SDL_GetVideoSurface()); while (1) { while (SDL_WaitEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_HELP) return; break; } } } } void XSDL_initSDL() { char fontpath[PATH_MAX]; SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK); __android_log_print(ANDROID_LOG_INFO, "XSDL", "Current video mode: %d %d", SDL_ListModes(NULL, 0)[0]->w, SDL_ListModes(NULL, 0)[0]->h); if( SDL_ListModes(NULL, 0)[0]->w > SDL_ListModes(NULL, 0)[0]->h ) SDL_SetVideoMode(VID_X, VID_Y, 0, SDL_SWSURFACE); else SDL_SetVideoMode(VID_Y, VID_X, 0, SDL_SWSURFACE); TTF_Init(); strcpy( fontpath, getenv("UNSECURE_STORAGE_DIR") ); strcat( fontpath, "/DroidSansMono.ttf" ); sFont = TTF_OpenFont(fontpath, 14); if (!sFont) { __android_log_print(ANDROID_LOG_INFO, "XSDL", "Error: cannot open font file, please reinstall the app"); exit(1); } } void XSDL_deinitSDL() { TTF_CloseFont(sFont); sFont = NULL; TTF_Quit(); // Do NOT call SDL_Quit(), it crashes! } void renderStringColor(const char *c, int x, int y, int r, int g, int b, SDL_Surface * surf) { if (!c || !c[0]) return; SDL_Color fColor = {r, g, b}; SDL_Rect fontRect = {0, 0, 0, 0}; SDL_Surface* fontSurface = TTF_RenderUTF8_Solid(sFont, c, fColor); fontRect.w = fontSurface->w; fontRect.h = fontSurface->h; fontRect.x = x - fontRect.w / 2; fontRect.y = y - fontRect.h / 2; SDL_BlitSurface(fontSurface, NULL, surf, &fontRect); SDL_FreeSurface(fontSurface); } void renderString(const char *c, int x, int y) { renderStringColor(c, x, y, 255, 255, 255, SDL_GetVideoSurface()); } void renderStringScaled(const char *c, int size, int x, int y, int r, int g, int b, SDL_Surface * surf) { if (!c || !c[0]) return; SDL_Color fColor = {r, g, b}; SDL_Rect fontRect = {0, 0, 0, 0}; TTF_Font* font = TTF_OpenFont("DroidSansMono.ttf", size); SDL_Surface* fontSurface = TTF_RenderUTF8_Solid(font, c, fColor); TTF_CloseFont(font); fontRect.w = fontSurface->w; fontRect.h = fontSurface->h; fontRect.x = x - fontRect.w / 2; fontRect.y = y - fontRect.h / 2; SDL_BlitSurface(fontSurface, NULL, surf, &fontRect); SDL_FreeSurface(fontSurface); }