From f2e95b6e6595bfe5c9a3e941a23854b7b4bf9ed4 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Tue, 24 Dec 2024 23:51:42 +0100 Subject: [PATCH 01/15] refactor(seekbars):migrate range values into xml files --- .../src/main/java/com/kdt/CustomSeekbar.java | 125 ++++++++++-------- .../main/java/net/kdt/pojavlaunch/Tools.java | 39 +++++- .../prefs/CustomSeekBarPreference.java | 8 +- .../prefs/QuickSettingSideDialog.java | 4 - .../LauncherPreferenceControlFragment.java | 7 - .../LauncherPreferenceJavaFragment.java | 11 +- .../LauncherPreferenceVideoFragment.java | 1 - .../main/res/layout/dialog_quick_setting.xml | 26 ++-- .../src/main/res/values/attributes.xml | 2 + .../src/main/res/values/values.xml | 26 ++++ .../src/main/res/xml/pref_control.xml | 14 ++ .../src/main/res/xml/pref_java.xml | 1 + .../src/main/res/xml/pref_video.xml | 1 + 13 files changed, 172 insertions(+), 93 deletions(-) diff --git a/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java b/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java index be80f33a88..5a240ee9f3 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java +++ b/app_pojavlauncher/src/main/java/com/kdt/CustomSeekbar.java @@ -3,6 +3,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; +import android.os.Build; import android.util.AttributeSet; import android.widget.SeekBar; @@ -19,8 +20,53 @@ public class CustomSeekbar extends SeekBar { private int mIncrement = 1; private SeekBar.OnSeekBarChangeListener mListener; - /** When using increments, this flag is used to prevent double calls to the listener */ - private boolean mInternalChanges = false; + private final OnSeekBarChangeListener mInternalListener = new OnSeekBarChangeListener() { + /** When using increments, this flag is used to prevent double calls to the listener */ + private boolean internalChanges = false; + /** Store the previous progress to prevent double calls with increments */ + private int previousProgress = 0; + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (internalChanges) return; + internalChanges = true; + + progress += mMin; + progress = applyIncrement(progress); + + if (progress != previousProgress) { + if (mListener != null) { + previousProgress = progress; + mListener.onProgressChanged(seekBar, progress, fromUser); + } + } + + // Forces the thumb to snap to the increment + setProgress(progress); + internalChanges = false; + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (internalChanges) return; + + if (mListener != null) { + mListener.onStartTrackingTouch(seekBar); + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + if (internalChanges) return; + internalChanges = true; + + setProgress(seekBar.getProgress()); + + if (mListener != null) { + mListener.onStopTrackingTouch(seekBar); + } + internalChanges = false; + } + }; public CustomSeekbar(Context context) { super(context); @@ -37,11 +83,6 @@ public CustomSeekbar(Context context, AttributeSet attrs, int defStyleAttr) { setup(attrs); } - public CustomSeekbar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setup(attrs); - } - public void setIncrement(int increment) { mIncrement = increment; } @@ -68,10 +109,15 @@ public synchronized int getProgress() { @Override public synchronized void setMin(int min) { - super.setMin(min); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + super.setMin(0); + } mMin = min; + //todo perform something to update the progress ? } + + /** * Wrapper to allow for a listener to be set around the internal listener */ @@ -82,54 +128,25 @@ public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) { public void setup(@Nullable AttributeSet attrs) { try (TypedArray attributes = getContext().obtainStyledAttributes(attrs, R.styleable.CustomSeekbar)) { - mIncrement = attributes.getInt(R.styleable.CustomSeekbar_seekBarIncrement, 1); - } - - super.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { - /** Store the previous progress to prevent double calls with increments */ - private int previousProgress = 0; - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (mInternalChanges) return; - mInternalChanges = true; - - progress += mMin; - progress = applyIncrement(progress); - - if (progress != previousProgress) { - if (mListener != null) { - previousProgress = progress; - mListener.onProgressChanged(seekBar, progress, fromUser); - } - } - - // Forces the thumb to snap to the increment - setProgress(progress); - mInternalChanges = false; + setIncrement(attributes.getInt(R.styleable.CustomSeekbar_seekBarIncrement, 1)); + int min = attributes.getInt(R.styleable.CustomSeekbar_android_min, 0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + super.setMin(0); } + setRange(min, super.getMax()); + } - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - if (mInternalChanges) return; - - if (mListener != null) { - mListener.onStartTrackingTouch(seekBar); - } - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (mInternalChanges) return; - mInternalChanges = true; - - setProgress(seekBar.getProgress()); - - if (mListener != null) { - mListener.onStopTrackingTouch(seekBar); - } - mInternalChanges = false; - } - }); + // Due to issues with negative progress when setting up the seekbar + // We need to set a random progress to force the refresh of the thumb + if(super.getProgress() == 0) { + super.setProgress(super.getProgress() + 1); + post(() -> { + super.setProgress(super.getProgress() - 1); + post(() -> super.setOnSeekBarChangeListener(mInternalListener)); + }); + } else { + super.setOnSeekBarChangeListener(mInternalListener); + } } /** diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index beb826c564..641f1d5b4a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -27,6 +27,7 @@ import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.Process; import android.provider.DocumentsContract; import android.provider.OpenableColumns; import android.util.ArrayMap; @@ -40,7 +41,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NotificationManagerCompat; @@ -538,7 +538,7 @@ private static void setFullscreenLegacy(Activity activity, boolean fullscreen) { visibilityChangeListener.onSystemUiVisibilityChange(decorView.getSystemUiVisibility()); //call it once since the UI state may not change after the call, so the activity wont become fullscreen } - @RequiresApi(Build.VERSION_CODES.R) + private static void setFullscreenSdk30(Activity activity, boolean fullscreen) { WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(activity.getWindow(), activity.getWindow().getDecorView()); @@ -555,25 +555,31 @@ private static void setFullscreenSdk30(Activity activity, boolean fullscreen) { ViewCompat.setOnApplyWindowInsetsListener( activity.getWindow().getDecorView(), (view, windowInsets) -> { - if (fullscreen && !activity.isInMultiWindowMode()) { + boolean fullscreenImpl = fullscreen; + if (SDK_INT >= Build.VERSION_CODES.N && activity.isInMultiWindowMode()) + fullscreenImpl = false; + + if (fullscreenImpl) { windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()); - activity.getWindow().setDecorFitsSystemWindows(false); } else { windowInsetsController.show(WindowInsetsCompat.Type.systemBars()); - activity.getWindow().setDecorFitsSystemWindows(true); } + if(SDK_INT >= Build.VERSION_CODES.R) + activity.getWindow().setDecorFitsSystemWindows(!fullscreenImpl); + return ViewCompat.onApplyWindowInsets(view, windowInsets); }); } public static void setFullscreen(Activity activity, boolean fullscreen) { + setFullscreenSdk30(activity, fullscreen); + /* if (SDK_INT >= Build.VERSION_CODES.R) { - setFullscreenSdk30(activity, fullscreen); }else { setFullscreenLegacy(activity, fullscreen); - } + }*/ } public static DisplayMetrics currentDisplayMetrics; @@ -1343,4 +1349,23 @@ public static void dialogForceClose(Context ctx) { } }).show(); } + + public static void setThreadsPriority(int priority) { + Process.getThreadPriority(Process.myTid()); + + + + Map threads = Thread.getAllStackTraces(); + for (Thread thread : threads.keySet()) { + //Log.d("Tools, thread: ", thread.getName()); + Log.d("Tools, thread: ", thread + " group: " + thread.getThreadGroup()); + Log.d("Tools, thread: ", Arrays.toString(thread.getStackTrace())); + Log.d("Tools, thread: ", String.valueOf(thread.getState())); + try { + thread.setPriority(priority); + }catch (Exception e) { + Log.e("Tools: thread", "Failed to set priority", e); + } + } + } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java index 0978de9541..5bb1bd131f 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/CustomSeekBarPreference.java @@ -28,10 +28,10 @@ public class CustomSeekBarPreference extends SeekBarPreference { @SuppressLint("PrivateResource") public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes); - mMin = a.getInt(R.styleable.SeekBarPreference_min, 0); - a.recycle(); + try (TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes)) { + mMin = a.getInt(R.styleable.SeekBarPreference_min, 0); + } } public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java index 3cf0131dfd..5458dbf572 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/QuickSettingSideDialog.java @@ -122,7 +122,6 @@ private void setupListeners() { mEditor.putBoolean("disableGestures", isChecked); }); - mGyroSensitivityBar.setRange(25, 300); mGyroSensitivityBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { PREF_GYRO_SENSITIVITY = progress / 100f; mEditor.putInt("gyroSensitivity", progress); @@ -131,7 +130,6 @@ private void setupListeners() { mGyroSensitivityBar.setProgress((int) (mOriginalGyroSensitivity * 100f)); setSeekTextPercent(mGyroSensitivityText, mGyroSensitivityBar.getProgress()); - mMouseSpeedBar.setRange(25, 300); mMouseSpeedBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { PREF_MOUSESPEED = progress / 100f; mEditor.putInt("mousespeed", progress); @@ -140,7 +138,6 @@ private void setupListeners() { mMouseSpeedBar.setProgress((int) (mOriginalMouseSpeed * 100f)); setSeekTextPercent(mMouseSpeedText, mMouseSpeedBar.getProgress()); - mGestureDelayBar.setRange(100, 1000); mGestureDelayBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { PREF_LONGPRESS_TRIGGER = progress; mEditor.putInt("timeLongPressTrigger", progress); @@ -149,7 +146,6 @@ private void setupListeners() { mGestureDelayBar.setProgress(mOriginalGestureDelay); setSeekTextMillisecond(mGestureDelayText, mGestureDelayBar.getProgress()); - mResolutionBar.setRange(25, 100); mResolutionBar.setOnSeekBarChangeListener((SimpleSeekBarListener) (seekBar, progress, fromUser) -> { PREF_SCALE_FACTOR = progress/100f; mEditor.putInt("resolutionRatio", progress); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java index 88ac51ffd8..11f34e5dc2 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceControlFragment.java @@ -32,31 +32,26 @@ public void onCreatePreferences(Bundle b, String str) { CustomSeekBarPreference seek2 = requirePreference("timeLongPressTrigger", CustomSeekBarPreference.class); - seek2.setRange(100, 1000); seek2.setValue(longPressTrigger); seek2.setSuffix(" ms"); CustomSeekBarPreference seek3 = requirePreference("buttonscale", CustomSeekBarPreference.class); - seek3.setRange(80, 250); seek3.setValue(prefButtonSize); seek3.setSuffix(" %"); CustomSeekBarPreference seek4 = requirePreference("mousescale", CustomSeekBarPreference.class); - seek4.setRange(25, 300); seek4.setValue(mouseScale); seek4.setSuffix(" %"); CustomSeekBarPreference seek6 = requirePreference("mousespeed", CustomSeekBarPreference.class); - seek6.setRange(25, 300); seek6.setValue((int)(mouseSpeed *100f)); seek6.setSuffix(" %"); CustomSeekBarPreference deadzoneSeek = requirePreference("gamepad_deadzone_scale", CustomSeekBarPreference.class); - deadzoneSeek.setRange(50, 200); deadzoneSeek.setValue((int) (joystickDeadzone * 100f)); deadzoneSeek.setSuffix(" %"); @@ -71,13 +66,11 @@ public void onCreatePreferences(Bundle b, String str) { CustomSeekBarPreference gyroSensitivitySeek = requirePreference("gyroSensitivity", CustomSeekBarPreference.class); - gyroSensitivitySeek.setRange(25, 300); gyroSensitivitySeek.setValue((int) (gyroSpeed*100f)); gyroSensitivitySeek.setSuffix(" %"); CustomSeekBarPreference gyroSampleRateSeek = requirePreference("gyroSampleRate", CustomSeekBarPreference.class); - gyroSampleRateSeek.setRange(5, 50); gyroSampleRateSeek.setValue(gyroSampleRate); gyroSampleRateSeek.setSuffix(" ms"); computeVisibility(); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java index 13c5b26a92..245bf84855 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceJavaFragment.java @@ -29,19 +29,18 @@ public void onCreatePreferences(Bundle b, String str) { // Triggers a write for some reason addPreferencesFromResource(R.xml.pref_java); - CustomSeekBarPreference seek7 = requirePreference("allocation", + CustomSeekBarPreference memorySeekbar = requirePreference("allocation", CustomSeekBarPreference.class); int maxRAM; - int deviceRam = getTotalDeviceMemory(seek7.getContext()); + int deviceRam = getTotalDeviceMemory(memorySeekbar.getContext()); if(is32BitsDevice() || deviceRam < 2048) maxRAM = Math.min(1024, deviceRam); else maxRAM = deviceRam - (deviceRam < 3064 ? 800 : 1024); //To have a minimum for the device to breathe - seek7.setMin(256); - seek7.setMax(maxRAM); - seek7.setValue(ramAllocation); - seek7.setSuffix(" MB"); + memorySeekbar.setMax(maxRAM); + memorySeekbar.setValue(ramAllocation); + memorySeekbar.setSuffix(" MB"); EditTextPreference editJVMArgs = findPreference("javaArgs"); if (editJVMArgs != null) { diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java index 4b2bc5279b..14f412e513 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/prefs/screens/LauncherPreferenceVideoFragment.java @@ -27,7 +27,6 @@ public void onCreatePreferences(Bundle b, String str) { CustomSeekBarPreference resolutionSeekbar = requirePreference("resolutionRatio", CustomSeekBarPreference.class); - resolutionSeekbar.setMin(25); resolutionSeekbar.setSuffix(" %"); // #724 bug fix diff --git a/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml b/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml index 211e63dd7e..b85a83f7c2 100644 --- a/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml +++ b/app_pojavlauncher/src/main/res/layout/dialog_quick_setting.xml @@ -26,7 +26,7 @@ android:id="@+id/editResolution_seekbar" android:layout_width="0dp" android:layout_height="@dimen/_36sdp" - + android:min="@integer/resolution_seekbar_min" app:seekBarIncrement="@integer/resolution_seekbar_increment" app:layout_constraintEnd_toEndOf="parent" @@ -36,9 +36,9 @@ + + \ No newline at end of file diff --git a/app_pojavlauncher/src/main/res/values/values.xml b/app_pojavlauncher/src/main/res/values/values.xml index af6989ff31..3220345b68 100644 --- a/app_pojavlauncher/src/main/res/values/values.xml +++ b/app_pojavlauncher/src/main/res/values/values.xml @@ -1,11 +1,37 @@ 5 + 25 + 10 + 100 + 1000 + + 5 + 80 + 250 + 5 + 25 + 300 + 5 + 25 + 300 + 5 + 25 + 300 + + 5 + 50 + 5 + 50 + 200 + 8 + 256 + \ No newline at end of file diff --git a/app_pojavlauncher/src/main/res/xml/pref_control.xml b/app_pojavlauncher/src/main/res/xml/pref_control.xml index e892adcfad..0d2778e173 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_control.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_control.xml @@ -35,6 +35,8 @@ android:title="@string/mcl_setting_title_longpresstrigger" app2:showSeekBarValue="true" app2:selectable="false" + app2:min="@integer/gesture_delay_seekbar_min" + android:max="@integer/gesture_delay_seekbar_max" app2:seekBarIncrement="@integer/gesture_delay_seekbar_increment" android:icon="@drawable/ic_setting_gesture_time" /> @@ -50,6 +52,8 @@ android:summary="@string/mcl_setting_subtitle_buttonscale" app2:showSeekBarValue="true" app2:selectable="false" + app2:min="@integer/button_scale_seekbar_min" + android:max="@integer/button_scale_seekbar_max" app2:seekBarIncrement="@integer/button_scale_seekbar_increment" android:icon="@drawable/ic_setting_control_scale" /> @@ -71,6 +75,8 @@ android:title="@string/mcl_setting_title_mousescale" app2:selectable="false" + app2:min="@integer/mouse_scale_seekbar_min" + android:max="@integer/mouse_scale_seekbar_max" app2:seekBarIncrement="@integer/mouse_scale_seekbar_increment" app2:showSeekBarValue="true" android:icon="@drawable/ic_setting_pointer_scale" @@ -82,6 +88,8 @@ android:title="@string/mcl_setting_title_mousespeed" android:icon="@drawable/ic_setting_mouse_speed" app2:selectable="false" + app2:min="@integer/mouse_speed_seekbar_min" + android:max="@integer/mouse_speed_seekbar_max" app2:seekBarIncrement="@integer/mouse_speed_seekbar_increment" app2:showSeekBarValue="true" /> diff --git a/app_pojavlauncher/src/main/res/xml/pref_java.xml b/app_pojavlauncher/src/main/res/xml/pref_java.xml index 239836cd7d..6a23c06ff5 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_java.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_java.xml @@ -25,6 +25,7 @@ android:summary="@string/mcl_memory_allocation_subtitle" android:title="@string/mcl_memory_allocation" app2:showSeekBarValue="true" + app2:min="@integer/memory_seekbar_min" app2:seekBarIncrement="@integer/memory_seekbar_increment" app2:selectable="false"/> diff --git a/app_pojavlauncher/src/main/res/xml/pref_video.xml b/app_pojavlauncher/src/main/res/xml/pref_video.xml index e791b9885e..76032bd0a4 100644 --- a/app_pojavlauncher/src/main/res/xml/pref_video.xml +++ b/app_pojavlauncher/src/main/res/xml/pref_video.xml @@ -26,6 +26,7 @@ android:title="@string/mcl_setting_title_resolution_scaler" app2:showSeekBarValue="true" app2:selectable="false" + app2:min="@integer/resolution_seekbar_min" app2:seekBarIncrement="@integer/resolution_seekbar_increment" android:icon="@drawable/ic_setting_screen_resolution" /> From 9a7fb2ae69fa500ccb8acbbbcb5f8a471428ddbc Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Wed, 25 Dec 2024 19:40:20 +0100 Subject: [PATCH 02/15] cleanup(tools): remove unused code related to fullscreen --- .../main/java/net/kdt/pojavlaunch/Tools.java | 37 +------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index 641f1d5b4a..5e20e9b839 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -513,33 +513,7 @@ public static DisplayMetrics getDisplayMetrics(Activity activity) { return displayMetrics; } - @SuppressWarnings("deprecation") - private static void setFullscreenLegacy(Activity activity, boolean fullscreen) { - final View decorView = activity.getWindow().getDecorView(); - View.OnSystemUiVisibilityChangeListener visibilityChangeListener = visibility -> { - boolean multiWindowMode = SDK_INT >= 24 && activity.isInMultiWindowMode(); - // When in multi-window mode, asking for fullscreen makes no sense (cause the launcher runs in a window) - // So, ignore the fullscreen setting when activity is in multi window mode - if(fullscreen && !multiWindowMode){ - if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - }else{ - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); - } - - }; - decorView.setOnSystemUiVisibilityChangeListener(visibilityChangeListener); - visibilityChangeListener.onSystemUiVisibilityChange(decorView.getSystemUiVisibility()); //call it once since the UI state may not change after the call, so the activity wont become fullscreen - } - - - private static void setFullscreenSdk30(Activity activity, boolean fullscreen) { + public static void setFullscreen(Activity activity, boolean fullscreen) { WindowInsetsControllerCompat windowInsetsController = WindowCompat.getInsetsController(activity.getWindow(), activity.getWindow().getDecorView()); if (windowInsetsController == null) { @@ -573,15 +547,6 @@ private static void setFullscreenSdk30(Activity activity, boolean fullscreen) { } - public static void setFullscreen(Activity activity, boolean fullscreen) { - setFullscreenSdk30(activity, fullscreen); - /* - if (SDK_INT >= Build.VERSION_CODES.R) { - }else { - setFullscreenLegacy(activity, fullscreen); - }*/ - } - public static DisplayMetrics currentDisplayMetrics; public static void updateWindowSize(Activity activity) { From 99c8ea2bfdd0b8e9ea3251865c38bc98055398d0 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Wed, 25 Dec 2024 19:45:33 +0100 Subject: [PATCH 03/15] cleanup(tools): remove unused code --- .../main/java/net/kdt/pojavlaunch/Tools.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index 5e20e9b839..316dc08220 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -1314,23 +1314,4 @@ public static void dialogForceClose(Context ctx) { } }).show(); } - - public static void setThreadsPriority(int priority) { - Process.getThreadPriority(Process.myTid()); - - - - Map threads = Thread.getAllStackTraces(); - for (Thread thread : threads.keySet()) { - //Log.d("Tools, thread: ", thread.getName()); - Log.d("Tools, thread: ", thread + " group: " + thread.getThreadGroup()); - Log.d("Tools, thread: ", Arrays.toString(thread.getStackTrace())); - Log.d("Tools, thread: ", String.valueOf(thread.getState())); - try { - thread.setPriority(priority); - }catch (Exception e) { - Log.e("Tools: thread", "Failed to set priority", e); - } - } - } } From 5d80d9baecd3892dd1bced2a46a2fb3c8b64d8f5 Mon Sep 17 00:00:00 2001 From: Maksim Belov <45949002+artdeell@users.noreply.github.com> Date: Mon, 30 Dec 2024 20:31:59 +0300 Subject: [PATCH 04/15] Feat[downloader]: downloader improvements (#6428) - Add download size queries through a HEAD request - Use the file size for progress instead of file count when all file size are available - Add download speed meter --- .../pojavlaunch/mirrors/DownloadMirror.java | 23 ++++++- .../tasks/MinecraftDownloader.java | 68 ++++++++++++++----- .../pojavlaunch/tasks/SpeedCalculator.java | 44 ++++++++++++ .../kdt/pojavlaunch/utils/DownloadUtils.java | 17 +++++ .../src/main/res/values/strings.xml | 3 +- 5 files changed, 136 insertions(+), 19 deletions(-) create mode 100644 app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/SpeedCalculator.java diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/mirrors/DownloadMirror.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/mirrors/DownloadMirror.java index d53b0617ea..f24159a328 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/mirrors/DownloadMirror.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/mirrors/DownloadMirror.java @@ -43,7 +43,7 @@ public static void downloadFileMirrored(int downloadClass, String urlInput, File return; }catch (FileNotFoundException e) { Log.w("DownloadMirror", "Cannot find the file on the mirror", e); - Log.i("DownloadMirror", "Failling back to default source"); + Log.i("DownloadMirror", "Falling back to default source"); } DownloadUtils.downloadFileMonitored(urlInput, outputFile, buffer, monitor); } @@ -63,11 +63,30 @@ public static void downloadFileMirrored(int downloadClass, String urlInput, File return; }catch (FileNotFoundException e) { Log.w("DownloadMirror", "Cannot find the file on the mirror", e); - Log.i("DownloadMirror", "Failling back to default source"); + Log.i("DownloadMirror", "Falling back to default source"); } DownloadUtils.downloadFile(urlInput, outputFile); } + /** + * Get the content length of a file on the current mirror. If the file is missing on the mirror, + * or the mirror does not give out the length, request the length from the original source + * @param downloadClass Class of the download. Can either be DOWNLOAD_CLASS_LIBRARIES, + * DOWNLOAD_CLASS_METADATA or DOWNLOAD_CLASS_ASSETS + * @param urlInput The original (Mojang) URL for the download + * @return the length of the file denoted by the URL in bytes, or -1 if not available + */ + public static long getContentLengthMirrored(int downloadClass, String urlInput) throws IOException { + long length = DownloadUtils.getContentLength(getMirrorMapping(downloadClass, urlInput)); + if(length < 1) { + Log.w("DownloadMirror", "Unable to get content length from mirror"); + Log.i("DownloadMirror", "Falling back to default source"); + return DownloadUtils.getContentLength(urlInput); + }else { + return length; + } + } + /** * Check if the current download source is a mirror and not an official source. * @return true if the source is a mirror, false otherwise diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloader.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloader.java index 5abaa57fdb..95074cc804 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloader.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/MinecraftDownloader.java @@ -37,14 +37,18 @@ import java.util.concurrent.atomic.AtomicReference; public class MinecraftDownloader { + private static final double ONE_MEGABYTE = (1024d * 1024d); public static final String MINECRAFT_RES = "https://resources.download.minecraft.net/"; private AtomicReference mDownloaderThreadException; private ArrayList mScheduledDownloadTasks; - private AtomicLong mDownloadFileCounter; - private AtomicLong mDownloadSizeCounter; - private long mDownloadFileCount; + private AtomicLong mProcessedFileCounter; + private AtomicLong mProcessedSizeCounter; // Total bytes of processed files (passed SHA1 or downloaded) + private AtomicLong mInternetUsageCounter; // How many bytes downloaded over Internet + private long mTotalFileCount; + private long mTotalSize; private File mSourceJarFile; // The source client JAR picked during the inheritance process private File mTargetJarFile; // The destination client JAR to which the source will be copied to. + private boolean mUseFileCounter; // Whether a file counter or a size counter should be used for progress private static final ThreadLocal sThreadLocalDownloadBuffer = new ThreadLocal<>(); @@ -80,12 +84,15 @@ private void downloadGame(Activity activity, JMinecraftVersionList.Version verIn // Put up a dummy progress line, for the activity to start the service and do all the other necessary // work to keep the launcher alive. We will replace this line when we will start downloading stuff. ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, 0, R.string.newdl_starting); + SpeedCalculator speedCalculator = new SpeedCalculator(); mTargetJarFile = createGameJarPath(versionName); mScheduledDownloadTasks = new ArrayList<>(); - mDownloadFileCounter = new AtomicLong(0); - mDownloadSizeCounter = new AtomicLong(0); + mProcessedFileCounter = new AtomicLong(0); + mProcessedSizeCounter = new AtomicLong(0); + mInternetUsageCounter = new AtomicLong(0); mDownloaderThreadException = new AtomicReference<>(null); + mUseFileCounter = false; if(!downloadAndProcessMetadata(activity, verInfo, versionName)) { throw new RuntimeException(activity.getString(R.string.exception_failed_to_unpack_jre17)); @@ -104,11 +111,9 @@ private void downloadGame(Activity activity, JMinecraftVersionList.Version verIn try { while (mDownloaderThreadException.get() == null && !downloaderPool.awaitTermination(33, TimeUnit.MILLISECONDS)) { - long dlFileCounter = mDownloadFileCounter.get(); - int progress = (int)((dlFileCounter * 100L) / mDownloadFileCount); - ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, progress, - R.string.newdl_downloading_game_files, dlFileCounter, - mDownloadFileCount, (double)mDownloadSizeCounter.get() / (1024d * 1024d)); + double speed = speedCalculator.feed(mInternetUsageCounter.get()) / ONE_MEGABYTE; + if(mUseFileCounter) reportProgressFileCounter(speed); + else reportProgressSizeCounter(speed); } Exception thrownException = mDownloaderThreadException.get(); if(thrownException != null) { @@ -123,6 +128,23 @@ private void downloadGame(Activity activity, JMinecraftVersionList.Version verIn } } + private void reportProgressFileCounter(double speed) { + long dlFileCounter = mProcessedFileCounter.get(); + int progress = (int)((dlFileCounter * 100L) / mTotalFileCount); + ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, progress, + R.string.newdl_downloading_game_files, dlFileCounter, + mTotalFileCount, speed); + } + + private void reportProgressSizeCounter(double speed) { + long dlFileSize = mProcessedSizeCounter.get(); + double dlSizeMegabytes = (double) dlFileSize / ONE_MEGABYTE; + double dlTotalMegabytes = (double) mTotalSize / ONE_MEGABYTE; + int progress = (int)((dlFileSize * 100L) / mTotalSize); + ProgressLayout.setProgress(ProgressLayout.DOWNLOAD_MINECRAFT, progress, + R.string.newdl_downloading_game_files_size, dlSizeMegabytes, dlTotalMegabytes, speed); + } + private File createGameJsonPath(String versionId) { return new File(Tools.DIR_HOME_VERSION, versionId + File.separator + versionId + ".json"); } @@ -233,7 +255,19 @@ private void growDownloadList(int addedElementCount) { private void scheduleDownload(File targetFile, int downloadClass, String url, String sha1, long size, boolean skipIfFailed) throws IOException { FileUtils.ensureParentDirectory(targetFile); - mDownloadFileCount++; + mTotalFileCount++; + if(size < 0) { + size = DownloadMirror.getContentLengthMirrored(downloadClass, url); + } + if(size < 0) { + // If we were unable to get the content length ourselves, we automatically fall back + // to tracking the progress using the file counter. + size = 0; + mUseFileCounter = true; + Log.i("MinecraftDownloader", "Failed to determine size of "+targetFile.getName()+", switching to file counter"); + }else { + mTotalSize += size; + } mScheduledDownloadTasks.add( new DownloaderTask(targetFile, downloadClass, url, sha1, size, skipIfFailed) ); @@ -401,18 +435,20 @@ private void downloadFile() throws Exception { }catch (Exception e) { if(!mSkipIfFailed) throw e; } - mDownloadFileCounter.incrementAndGet(); + mProcessedFileCounter.incrementAndGet(); } private void finishWithoutDownloading() { - mDownloadFileCounter.incrementAndGet(); - mDownloadSizeCounter.addAndGet(mDownloadSize); + mProcessedFileCounter.incrementAndGet(); + mProcessedSizeCounter.addAndGet(mDownloadSize); } @Override public void updateProgress(int curr, int max) { - mDownloadSizeCounter.addAndGet(curr - mLastCurr); - mLastCurr = curr; + int delta = curr - mLastCurr; + mProcessedSizeCounter.addAndGet(delta); + mInternetUsageCounter.addAndGet(delta); + mLastCurr = curr; } } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/SpeedCalculator.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/SpeedCalculator.java new file mode 100644 index 0000000000..136b0c1789 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/SpeedCalculator.java @@ -0,0 +1,44 @@ +package net.kdt.pojavlaunch.tasks; + +/** + * A simple class to calculate the average Internet speed using a simple moving average. + */ +public class SpeedCalculator { + private long mLastMillis; + private long mLastBytes; + private int mIndex; + private final double[] mPreviousInputs; + private double mSum; + + public SpeedCalculator() { + this(64); + } + + public SpeedCalculator(int averageDepth) { + mPreviousInputs = new double[averageDepth]; + } + + private double addToAverage(double speed) { + mSum -= mPreviousInputs[mIndex]; + mSum += speed; + mPreviousInputs[mIndex] = speed; + if(++mIndex == mPreviousInputs.length) mIndex = 0; + double dLength = mPreviousInputs.length; + return (mSum + (dLength / 2d)) / dLength; + } + + /** + * Update the current amount of bytes downloaded. + * @param bytes the new amount of bytes downloaded + * @return the current download speed in bytes per second + */ + public double feed(long bytes) { + long millis = System.currentTimeMillis(); + long deltaBytes = bytes - mLastBytes; + long deltaMillis = millis - mLastMillis; + mLastBytes = bytes; + mLastMillis = millis; + double speed = (double)deltaBytes / ((double)deltaMillis / 1000d); + return addToAverage(speed); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java index b4df71409a..dbdbadaf07 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/DownloadUtils.java @@ -155,6 +155,23 @@ public static T ensureSha1(File outputFile, @Nullable String sha1, Callable< return result; } + /** + * Get the content length for a given URL. + * @param url the URL to get the length for + * @return the length in bytes or -1 if not available + * @throws IOException if an I/O error occurs. + */ + public static long getContentLength(String url) throws IOException { + HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); + urlConnection.setRequestMethod("HEAD"); + urlConnection.setDoInput(false); + urlConnection.setDoOutput(false); + urlConnection.connect(); + int responseCode = urlConnection.getResponseCode(); + if(responseCode >= 200 && responseCode <= 299) return urlConnection.getContentLength(); + return -1; + } + public interface ParseCallback { T process(String input) throws ParseException; } diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 91104432dc..8814ef333a 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -374,7 +374,8 @@ Failed to install JRE 17 Reading game metadata… Downloading game metadata (%s) - Downloading game files… (%d/%d, %.2f MB) + Downloading game… (%d/%d, %.2f MB/s) + Downloading game… (%.2f/%.2f MB, %.2f MB/s) Select image region V. lock H. lock From a77271df03d38351507a9139c8a3d6b35cf917ea Mon Sep 17 00:00:00 2001 From: movte <212824502@qq.com> Date: Tue, 17 Dec 2024 21:27:18 +0800 Subject: [PATCH 05/15] Fix the issue where the long-press trigger delay does not refresh properly. --- .../customcontrols/mouse/LeftClickGesture.java | 7 ++++++- .../customcontrols/mouse/RightClickGesture.java | 9 +++++++-- .../customcontrols/mouse/ValidatorGesture.java | 12 +++++++----- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java index 38ffef803b..ef93c222bb 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java @@ -17,7 +17,7 @@ public class LeftClickGesture extends ValidatorGesture { private boolean mMouseActivated; public LeftClickGesture(Handler handler) { - super(handler, LauncherPreferences.PREF_LONGPRESS_TRIGGER); + super(handler); } public final void inputEvent() { @@ -27,6 +27,11 @@ public final void inputEvent() { } } + @Override + protected int getDelayValue() { + return LauncherPreferences.PREF_LONGPRESS_TRIGGER; + } + @Override public boolean checkAndTrigger() { boolean fingerStill = LeftClickGesture.isFingerStill(mGestureStartX, mGestureStartY, mGestureEndX, mGestureEndY, FINGER_STILL_THRESHOLD); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java index d24874f7ed..39dfa965fb 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java @@ -6,12 +6,12 @@ import org.lwjgl.glfw.CallbackBridge; -public class RightClickGesture extends ValidatorGesture{ +public class RightClickGesture extends ValidatorGesture { private boolean mGestureEnabled = true; private boolean mGestureValid = true; private float mGestureStartX, mGestureStartY, mGestureEndX, mGestureEndY; public RightClickGesture(Handler mHandler) { - super(mHandler, 150); + super(mHandler); } public final void inputEvent() { @@ -29,6 +29,11 @@ public void setMotion(float deltaX, float deltaY) { mGestureEndY += deltaY; } + @Override + protected int getDelayValue() { + return 150; + } + @Override public boolean checkAndTrigger() { // If the validate() method was called, it means that the user held on for too long. The cancellation should be ignored. diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java index 20956d5675..c43817e05b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java @@ -9,16 +9,13 @@ public abstract class ValidatorGesture implements Runnable{ private final Handler mHandler; private boolean mGestureActive; - private final int mRequiredDuration; /** * @param mHandler the Handler that will be used for calling back the checkAndTrigger() method. * This Handler should run on the same thread as the callee of submit()/cancel() - * @param mRequiredDuration the duration after which the class will call checkAndTrigger(). */ - public ValidatorGesture(Handler mHandler, int mRequiredDuration) { + public ValidatorGesture(Handler mHandler) { this.mHandler = mHandler; - this.mRequiredDuration = mRequiredDuration; } /** @@ -28,7 +25,7 @@ public ValidatorGesture(Handler mHandler, int mRequiredDuration) { */ public final boolean submit() { if(mGestureActive) return false; - mHandler.postDelayed(this, mRequiredDuration); + mHandler.postDelayed(this, getDelayValue()); mGestureActive = true; return true; } @@ -54,6 +51,11 @@ public final void run() { onGestureCancelled(false); } + /** + * @return the duration after which the class will call checkAndTrigger(). + */ + protected abstract int getDelayValue(); + /** * This method will be called after mRequiredDuration milliseconds, if the gesture was not cancelled. * @return false if you want to mark this gesture as "inactive" From 93924b429b7213e78c41f69350a3d6ce4dd69289 Mon Sep 17 00:00:00 2001 From: artdeell Date: Thu, 2 Jan 2025 18:53:35 +0300 Subject: [PATCH 06/15] Fix[gestures]: rename method and improve documentation --- .../customcontrols/mouse/LeftClickGesture.java | 2 +- .../customcontrols/mouse/RightClickGesture.java | 2 +- .../customcontrols/mouse/ValidatorGesture.java | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java index ef93c222bb..9a4c20b447 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java @@ -28,7 +28,7 @@ public final void inputEvent() { } @Override - protected int getDelayValue() { + protected int getCheckDuration() { return LauncherPreferences.PREF_LONGPRESS_TRIGGER; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java index 39dfa965fb..158634b629 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java @@ -30,7 +30,7 @@ public void setMotion(float deltaX, float deltaY) { } @Override - protected int getDelayValue() { + protected int getCheckDuration() { return 150; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java index c43817e05b..5ed9da5f36 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java @@ -25,7 +25,7 @@ public ValidatorGesture(Handler mHandler) { */ public final boolean submit() { if(mGestureActive) return false; - mHandler.postDelayed(this, getDelayValue()); + mHandler.postDelayed(this, getCheckDuration()); mGestureActive = true; return true; } @@ -52,12 +52,13 @@ public final void run() { } /** - * @return the duration after which the class will call checkAndTrigger(). + * This method will be called during gesture submission to determine the gesture check duration. + * @return the required gesture check duration in milliseconds */ - protected abstract int getDelayValue(); + protected abstract int getCheckDuration(); /** - * This method will be called after mRequiredDuration milliseconds, if the gesture was not cancelled. + * This method will be called after getCheckDuration() milliseconds, if the gesture was not cancelled. * @return false if you want to mark this gesture as "inactive" * true otherwise */ From dd15d8a46960b1bd61c803d032abad74853b196c Mon Sep 17 00:00:00 2001 From: artdeell Date: Thu, 2 Jan 2025 21:34:25 +0300 Subject: [PATCH 07/15] Style[gestures]: rename method to getGestureDelay --- .../pojavlaunch/customcontrols/mouse/LeftClickGesture.java | 2 +- .../pojavlaunch/customcontrols/mouse/RightClickGesture.java | 2 +- .../pojavlaunch/customcontrols/mouse/ValidatorGesture.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java index 9a4c20b447..259117ff51 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/LeftClickGesture.java @@ -28,7 +28,7 @@ public final void inputEvent() { } @Override - protected int getCheckDuration() { + protected int getGestureDelay() { return LauncherPreferences.PREF_LONGPRESS_TRIGGER; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java index 158634b629..3ca4c02ea3 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java @@ -30,7 +30,7 @@ public void setMotion(float deltaX, float deltaY) { } @Override - protected int getCheckDuration() { + protected int getGestureDelay() { return 150; } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java index 5ed9da5f36..a61e45c67c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/ValidatorGesture.java @@ -25,7 +25,7 @@ public ValidatorGesture(Handler mHandler) { */ public final boolean submit() { if(mGestureActive) return false; - mHandler.postDelayed(this, getCheckDuration()); + mHandler.postDelayed(this, getGestureDelay()); mGestureActive = true; return true; } @@ -55,10 +55,10 @@ public final void run() { * This method will be called during gesture submission to determine the gesture check duration. * @return the required gesture check duration in milliseconds */ - protected abstract int getCheckDuration(); + protected abstract int getGestureDelay(); /** - * This method will be called after getCheckDuration() milliseconds, if the gesture was not cancelled. + * This method will be called after getGestureDelay() milliseconds, if the gesture was not cancelled. * @return false if you want to mark this gesture as "inactive" * true otherwise */ From 80e0a6aac8ef542cac1c65bb6f10c8eb9373dc2c Mon Sep 17 00:00:00 2001 From: Jordan Date: Thu, 2 Jan 2025 15:00:03 -0600 Subject: [PATCH 08/15] Update GPLAY_PRIVACY_POLICY (#6174) Update GPLAY_PRIVACY_POLICY to be more clear, and remove the news entry as the news in Pojav no longer exists. --- GPLAY_PRIVACY_POLICY | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GPLAY_PRIVACY_POLICY b/GPLAY_PRIVACY_POLICY index ef9a8a43d1..f55c08fde5 100644 --- a/GPLAY_PRIVACY_POLICY +++ b/GPLAY_PRIVACY_POLICY @@ -1,3 +1,3 @@ -1. This app (while idle) does NOT collect any sensitive information, and does NOT use network (exception is the "News" page, it uses network to load the launcher news) +1. This app (while idle) does NOT collect any sensitive information, and uses network for downloading Minecraft resources. 2. While running Minecraft, app also does NOT collect any sensitive information about your device. Snooper by Mojang does. 3. Some sensitive data is stored in crash reports after the game crashes, but it's not being shared to anyone except the current user. From 2e596cd07f1ec9d3f774f1239949db8b7e13e3e8 Mon Sep 17 00:00:00 2001 From: Maksim Belov <45949002+artdeell@users.noreply.github.com> Date: Sat, 4 Jan 2025 02:41:02 +0300 Subject: [PATCH 09/15] Fix[renderer]: move GL4ES initialization code (#6447) --- .../src/main/jni/ctxbridges/gl_bridge.c | 40 ++++++++----------- .../java/org/lwjgl/opengl/GLCapabilities.java | 2 + .../org/lwjgl/opengl/PojavRendererInit.java | 39 ++++++++++++++++++ 3 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/PojavRendererInit.java diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c b/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c index 89cc44bc79..8f66139735 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c @@ -55,25 +55,6 @@ static void gl4esi_get_display_dimensions(int* width, int* height) { *height = 0; } -static bool already_initialized = false; -static void gl_init_gl4es_internals() { - if(already_initialized) return; - already_initialized = true; - void* gl4es = dlopen("libgl4es_114.so", RTLD_NOLOAD); - if(gl4es == NULL) return; - void (*set_getmainfbsize)(void (*new_getMainFBSize)(int* width, int* height)); - set_getmainfbsize = dlsym(gl4es, "set_getmainfbsize"); - if(set_getmainfbsize == NULL) goto warn; - set_getmainfbsize(gl4esi_get_display_dimensions); - goto cleanup; - - warn: - printf("gl4esinternals warning: gl4es was found but internals not initialized. expect rendering issues.\n"); - cleanup: - // dlclose just decreases a ref counter, so this is fine - dlclose(gl4es); -} - gl_render_window_t* gl_init_context(gl_render_window_t *share) { gl_render_window_t* bundle = malloc(sizeof(gl_render_window_t)); memset(bundle, 0, sizeof(gl_render_window_t)); @@ -145,10 +126,6 @@ void gl_swap_surface(gl_render_window_t* bundle) { } void gl_make_current(gl_render_window_t* bundle) { - // Perform initialization here as the renderer may not be loaded when gl_init or gl_init_context is called. - // Yes, even though it is dlopened on MC startup by Pojav, due to linker namespacing weirdness - // on API 29/MIUI it may not be loaded at the point of the gl_init call in the current namespace. - gl_init_gl4es_internals(); if(bundle == NULL) { if(eglMakeCurrent_p(g_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { @@ -211,3 +188,20 @@ void gl_swap_interval(int swapInterval) { eglSwapInterval_p(g_EglDisplay, swapInterval); } + +JNIEXPORT void JNICALL +Java_org_lwjgl_opengl_PojavRendererInit_nativeInitGl4esInternals(JNIEnv *env, jclass clazz, + jobject function_provider) { + __android_log_print(ANDROID_LOG_INFO, g_LogTag, "GL4ES internals initializing..."); + jclass funcProviderClass = (*env)->GetObjectClass(env, function_provider); + jmethodID method_getFunctionAddress = (*env)->GetMethodID(env, funcProviderClass, "getFunctionAddress", "(Ljava/lang/CharSequence;)J"); +#define GETSYM(N) ((*env)->CallLongMethod(env, function_provider, method_getFunctionAddress, (*env)->NewStringUTF(env, N))); + + void (*set_getmainfbsize)(void (*new_getMainFBSize)(int* width, int* height)) = (void*)GETSYM("set_getmainfbsize"); + if(set_getmainfbsize != NULL) { + __android_log_print(ANDROID_LOG_INFO, g_LogTag, "GL4ES internals initialized dimension callback"); + set_getmainfbsize(gl4esi_get_display_dimensions); + } + +#undef GETSYM +} diff --git a/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/GLCapabilities.java b/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/GLCapabilities.java index 861187a512..034e4e6bd5 100644 --- a/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/GLCapabilities.java +++ b/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/GLCapabilities.java @@ -4792,6 +4792,8 @@ public final class GLCapabilities { GLCapabilities(FunctionProvider provider, Set ext, boolean fc, IntFunction bufferFactory) { forwardCompatible = fc; + PojavRendererInit.onCreateCapabilities(provider); + PointerBuffer caps = bufferFactory.apply(ADDRESS_BUFFER_SIZE); OpenGL11 = check_GL11(provider, caps, ext, fc); diff --git a/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/PojavRendererInit.java b/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/PojavRendererInit.java new file mode 100644 index 0000000000..8a74020a72 --- /dev/null +++ b/jre_lwjgl3glfw/src/main/java/org/lwjgl/opengl/PojavRendererInit.java @@ -0,0 +1,39 @@ +package org.lwjgl.opengl; + +import org.lwjgl.system.FunctionProvider; +import org.lwjgl.system.SharedLibrary; + +import javax.annotation.Nullable; + +/** + * Class for initializing renderer-specific callbacks. Allows to reliably initialize + * any callbacks needed for renderers by using the same FunctionProvider as used for loading + * GL symbols. + * */ +public class PojavRendererInit { + + public static void onCreateCapabilities(FunctionProvider functionProvider) { + String rendererName = null; + if(functionProvider instanceof SharedLibrary) { + SharedLibrary rendererLibrary = (SharedLibrary) functionProvider; + rendererName = rendererLibrary.getName(); + } + if(!isValidString(rendererName)) { + rendererName = System.getProperty("org.lwjgl.opengl.libname"); + } + if(!isValidString(rendererName)) { + System.out.println("PojavRendererInit: Failed to find Pojav renderer name! " + + "Renderer-specific initialization may not work properly"); + } + // NOTE: hardcoded gl4es libname + if(rendererName.endsWith("libgl4es_114.so")) { + nativeInitGl4esInternals(functionProvider); + } + } + + private static boolean isValidString(@Nullable String s) { + return s != null && !s.isEmpty(); + } + + public static native void nativeInitGl4esInternals(FunctionProvider functionProvider); +} From a72451c9e7735df2d7000358489311784c896697 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Tue, 7 Jan 2025 20:21:59 +0100 Subject: [PATCH 10/15] fix(gesture): Right click not working Turns out if you were perfectly still, it would fail. For a quick tap, it was fairly easy to create. --- .../pojavlaunch/customcontrols/mouse/RightClickGesture.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java index 3ca4c02ea3..84c5efd183 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java @@ -17,14 +17,15 @@ public RightClickGesture(Handler mHandler) { public final void inputEvent() { if(!mGestureEnabled) return; if(submit()) { - mGestureStartX = CallbackBridge.mouseX; - mGestureStartY = CallbackBridge.mouseY; + mGestureStartX = mGestureEndX = CallbackBridge.mouseX; + mGestureStartY = mGestureEndY = CallbackBridge.mouseY; mGestureEnabled = false; mGestureValid = true; } } public void setMotion(float deltaX, float deltaY) { + System.out.println("set motion called"); mGestureEndX += deltaX; mGestureEndY += deltaY; } @@ -49,6 +50,7 @@ public void onGestureCancelled(boolean isSwitching) { mGestureEnabled = true; if(!mGestureValid || isSwitching) return; boolean fingerStill = LeftClickGesture.isFingerStill(mGestureStartX, mGestureStartY, mGestureEndX, mGestureEndY, LeftClickGesture.FINGER_STILL_THRESHOLD); + System.out.println("Right click: " + fingerStill); if(!fingerStill) return; CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, true); CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, false); From 041838725b1c5938c7611fa465a3e6959caccea7 Mon Sep 17 00:00:00 2001 From: artdeell Date: Sun, 5 Jan 2025 12:05:09 +0300 Subject: [PATCH 11/15] Fix[ffmpeg_plugin]: better FFmpeg plugin insertion NOTE: requires a different version of the FFmpeg plugin which I haven't made yet --- .../kdt/pojavlaunch/plugins/FFmpegPlugin.java | 8 ++++- .../net/kdt/pojavlaunch/utils/JREUtils.java | 2 +- .../src/main/jni/input_bridge_v3.c | 35 +++++++++++++------ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java index bd1b308124..514a176555 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java @@ -5,15 +5,21 @@ import android.content.pm.PackageManager; import android.util.Log; +import java.io.File; + public class FFmpegPlugin { public static boolean isAvailable = false; public static String libraryPath; + public static String executablePath; public static void discover(Context context) { PackageManager manager = context.getPackageManager(); try { PackageInfo ffmpegPluginInfo = manager.getPackageInfo("net.kdt.pojavlaunch.ffmpeg", PackageManager.GET_SHARED_LIBRARY_FILES); libraryPath = ffmpegPluginInfo.applicationInfo.nativeLibraryDir; - isAvailable = true; + File ffmpegExecutable = new File(libraryPath, "libffmpeg.so"); + executablePath = ffmpegExecutable.getAbsolutePath(); + // Older plugin versions still have the old executable location + isAvailable = ffmpegExecutable.exists(); }catch (Exception e) { Log.i("FFmpegPlugin", "Failed to discover plugin", e); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index 521c91de84..dde80ec37c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -215,7 +215,7 @@ public static void setJavaEnvironment(Activity activity, String jreHome) throws envMap.put("LD_LIBRARY_PATH", LD_LIBRARY_PATH); envMap.put("PATH", jreHome + "/bin:" + Os.getenv("PATH")); if(FFmpegPlugin.isAvailable) { - envMap.put("PATH", FFmpegPlugin.libraryPath+":"+envMap.get("PATH")); + envMap.put("POJAV_FFMPEG_PATH", FFmpegPlugin.executablePath); } if(LOCAL_RENDERER != null) { diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 03bb79f627..ce106f859c 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -230,23 +230,38 @@ void sendData(int type, int i1, int i2, int i3, int i4) { atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire); } +static jbyteArray stringToBytes(JNIEnv *env, const char* string) { + const jsize string_data_len = (jsize)(strlen(string) + 1); + jbyteArray result = (*env)->NewByteArray(env, (jsize)string_data_len); + (*env)->SetByteArrayRegion(env, result, 0, (jsize)string_data_len, (const jbyte*) string); + return result; +} + /** * Hooked version of java.lang.UNIXProcess.forkAndExec() - * which is used to handle the "open" command. + * which is used to handle the "open" command and "ffmpeg" invocations */ jint hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream) { - char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); - - // Here we only handle the "xdg-open" command - if (strcmp(basename(pProg), "xdg-open") != 0) { - (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); - return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); - } + const char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); + const char* pProgBaseName = basename(pProg); + const size_t basename_len = strlen(pProgBaseName); + char prog_basename[basename_len]; + memcpy(&prog_basename, pProgBaseName, basename_len + 1); (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); - Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); - return 0; + if(strcmp(prog_basename, "xdg-open") == 0) { + // When invoking xdg-open, send that open command into the android half instead + Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); + return 0; + }else if(strcmp(prog_basename, "ffmpeg") == 0) { + // When invoking ffmpeg, always replace the program path with the path to ffmpeg from the plugin. + const char* ffmpeg_path = getenv("POJAV_FFMPEG_PATH"); + if(ffmpeg_path != NULL) { + prog = stringToBytes(env, ffmpeg_path); + } + } + return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); } void hookExec() { From 6d39ab2d49696d3f5a00de06f0d9ff0b8b24bbf4 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Sun, 5 Jan 2025 19:03:01 +0300 Subject: [PATCH 12/15] Fix[ffmpeg_plugin]: replace LD_LIBRARY_PATH/PATH for ffmpeg, switch default exec mode --- .../net/kdt/pojavlaunch/utils/JREUtils.java | 3 +- app_pojavlauncher/src/main/jni/Android.mk | 1 + .../src/main/jni/input_bridge_v3.c | 52 ----------- .../src/main/jni/java_exec_hooks.c | 90 +++++++++++++++++++ 4 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 app_pojavlauncher/src/main/jni/java_exec_hooks.c diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index dde80ec37c..1b9a2cd2b4 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -368,7 +368,8 @@ public static List getJavaArgs(Context ctx, String runtimeHome, String u "-Dnet.minecraft.clientmodname=" + Tools.APP_NAME, "-Dfml.earlyprogresswindow=false", //Forge 1.14+ workaround - "-Dloader.disable_forked_guis=true" + "-Dloader.disable_forked_guis=true", + "-Djdk.lang.Process.launchMechanism=FORK" // Default is POSIX_SPAWN which requires starting jspawnhelper, which doesn't work on Android )); if(LauncherPreferences.PREF_ARC_CAPES) { overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector/arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176"); diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index f0948a8026..9ee43d7e75 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -45,6 +45,7 @@ LOCAL_SRC_FILES := \ jre_launcher.c \ utils.c \ stdio_is.c \ + java_exec_hooks.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index ce106f859c..951050a093 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -30,8 +30,6 @@ #define EVENT_TYPE_MOUSE_BUTTON 1006 #define EVENT_TYPE_SCROLL 1007 -jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream); - static void registerFunctions(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { @@ -230,56 +228,6 @@ void sendData(int type, int i1, int i2, int i3, int i4) { atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire); } -static jbyteArray stringToBytes(JNIEnv *env, const char* string) { - const jsize string_data_len = (jsize)(strlen(string) + 1); - jbyteArray result = (*env)->NewByteArray(env, (jsize)string_data_len); - (*env)->SetByteArrayRegion(env, result, 0, (jsize)string_data_len, (const jbyte*) string); - return result; -} - -/** - * Hooked version of java.lang.UNIXProcess.forkAndExec() - * which is used to handle the "open" command and "ffmpeg" invocations - */ -jint -hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream) { - const char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); - const char* pProgBaseName = basename(pProg); - const size_t basename_len = strlen(pProgBaseName); - char prog_basename[basename_len]; - memcpy(&prog_basename, pProgBaseName, basename_len + 1); - (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); - - if(strcmp(prog_basename, "xdg-open") == 0) { - // When invoking xdg-open, send that open command into the android half instead - Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); - return 0; - }else if(strcmp(prog_basename, "ffmpeg") == 0) { - // When invoking ffmpeg, always replace the program path with the path to ffmpeg from the plugin. - const char* ffmpeg_path = getenv("POJAV_FFMPEG_PATH"); - if(ffmpeg_path != NULL) { - prog = stringToBytes(env, ffmpeg_path); - } - } - return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); -} - -void hookExec() { - jclass cls; - orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_UNIXProcess_forkAndExec"); - if (!orig_ProcessImpl_forkAndExec) { - orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_ProcessImpl_forkAndExec"); - cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/ProcessImpl"); - } else { - cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/UNIXProcess"); - } - JNINativeMethod methods[] = { - {"forkAndExec", "(I[B[B[BI[BI[B[IZ)I", (void *)&hooked_ProcessImpl_forkAndExec} - }; - (*pojav_environ->runtimeJNIEnvPtr_JRE)->RegisterNatives(pojav_environ->runtimeJNIEnvPtr_JRE, cls, methods, 1); - printf("Registered forkAndExec\n"); -} - /** * Basically a verbatim implementation of ndlopen(), found at * https://github.com/PojavLauncherTeam/lwjgl3/blob/3.3.1/modules/lwjgl/core/src/generated/c/linux/org_lwjgl_system_linux_DynamicLinkLoader.c#L11 diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/java_exec_hooks.c new file mode 100644 index 0000000000..d0755a19b3 --- /dev/null +++ b/app_pojavlauncher/src/main/jni/java_exec_hooks.c @@ -0,0 +1,90 @@ +// +// Created by maks on 05.01.2025. +// + +#include +#include +#include +#include +#include + +#include +#include +#include + +static jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream); + +// Turn a C-style string into a Java byte array +static jbyteArray stringToBytes(JNIEnv *env, const char* string) { + const jsize string_data_len = (jsize)(strlen(string) + 1); + jbyteArray result = (*env)->NewByteArray(env, (jsize)string_data_len); + (*env)->SetByteArrayRegion(env, result, 0, (jsize)string_data_len, (const jbyte*) string); + return result; +} + +// Replace the env block with the one that has the desired LD_LIBRARY_PATH/PATH. +// (Due to my laziness this ignores the current contents of the block) +static void replaceLibPathInEnvBlock(JNIEnv *env, jbyteArray* envBlock, jint* envc, const char* directory) { + static bool env_block_replacement_warning = false; + if(*envBlock != NULL && !env_block_replacement_warning) { + printf("exec_hooks WARN: replaceLibPathInEnvBlock does not preserve original env. Please notify PojavLauncherTeam if you need that feature\n"); + env_block_replacement_warning = true; + } + char envStr[1024]; + jsize new_envl = snprintf(envStr, sizeof(envStr) / sizeof(char), "LD_LIBRARY_PATH=%s%cPATH=%s", directory, 0 ,directory) + 1; + jbyteArray newBlock = (*env)->NewByteArray(env, new_envl); + (*env)->SetByteArrayRegion(env, newBlock, 0, new_envl, (jbyte*) envStr); + *envBlock = newBlock; + *envc = 2; +} + +/** + * Hooked version of java.lang.UNIXProcess.forkAndExec() + * which is used to handle the "open" command and "ffmpeg" invocations + */ +static jint hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream) { + const char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); + const char* pProgBaseName = basename(pProg); + const size_t basename_len = strlen(pProgBaseName); + char prog_basename[basename_len]; + memcpy(&prog_basename, pProgBaseName, basename_len + 1); + (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); + + if(strcmp(prog_basename, "xdg-open") == 0) { + // When invoking xdg-open, send the open URL into Android + Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); + return 0; + }else if(strcmp(prog_basename, "ffmpeg") == 0) { + // When invoking ffmpeg, always replace the program path with the path to ffmpeg from the plugin. + // This allows us to replace the executable name, which is needed because android doesn't allow + // us to put files that don't start with "lib" and end with ".so" into folders that we can execute + // from + + // Also add LD_LIBRARY_PATH and PATH for the lib in order to override the ones from the launcher, since + // they may interfere with ffmpeg dependencies. + const char* ffmpeg_path = getenv("POJAV_FFMPEG_PATH"); + prog = NULL; + if(ffmpeg_path != NULL) { + replaceLibPathInEnvBlock(env, &envBlock, &envc, dirname(ffmpeg_path)); + prog = stringToBytes(env, ffmpeg_path); + } + } + return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); +} + +// Hook the forkAndExec method in the Java runtime for custom executable overriding. +void hookExec() { + jclass cls; + orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_UNIXProcess_forkAndExec"); + if (!orig_ProcessImpl_forkAndExec) { + orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_ProcessImpl_forkAndExec"); + cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/ProcessImpl"); + } else { + cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/UNIXProcess"); + } + JNINativeMethod methods[] = { + {"forkAndExec", "(I[B[B[BI[BI[B[IZ)I", (void *)&hooked_ProcessImpl_forkAndExec} + }; + (*pojav_environ->runtimeJNIEnvPtr_JRE)->RegisterNatives(pojav_environ->runtimeJNIEnvPtr_JRE, cls, methods, 1); + printf("Registered forkAndExec\n"); +} \ No newline at end of file From aadb91dc989225f84e319f48dbb0fee267aecc59 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Sun, 5 Jan 2025 19:11:30 +0300 Subject: [PATCH 13/15] Whoops[exec_hooks]: do not null program unconditionally --- app_pojavlauncher/src/main/jni/java_exec_hooks.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/java_exec_hooks.c index d0755a19b3..0972b3c73d 100644 --- a/app_pojavlauncher/src/main/jni/java_exec_hooks.c +++ b/app_pojavlauncher/src/main/jni/java_exec_hooks.c @@ -63,7 +63,6 @@ static jint hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mo // Also add LD_LIBRARY_PATH and PATH for the lib in order to override the ones from the launcher, since // they may interfere with ffmpeg dependencies. const char* ffmpeg_path = getenv("POJAV_FFMPEG_PATH"); - prog = NULL; if(ffmpeg_path != NULL) { replaceLibPathInEnvBlock(env, &envBlock, &envc, dirname(ffmpeg_path)); prog = stringToBytes(env, ffmpeg_path); From ebe5314f28df7d94c76384cb6ec463ff505ad0a0 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Mon, 6 Jan 2025 20:32:54 +0300 Subject: [PATCH 14/15] Feat[lwjgl]: add vulkan to lwjgl dlopen hook, move hook to new file --- app_pojavlauncher/src/main/jni/Android.mk | 1 + app_pojavlauncher/src/main/jni/egl_bridge.c | 14 ++-- .../src/main/jni/input_bridge_v3.c | 41 ------------ .../src/main/jni/lwjgl_dlopen_hook.c | 64 +++++++++++++++++++ 4 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 9ee43d7e75..a820a902a8 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -46,6 +46,7 @@ LOCAL_SRC_FILES := \ utils.c \ stdio_is.c \ java_exec_hooks.c \ + lwjgl_dlopen_hook.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) diff --git a/app_pojavlauncher/src/main/jni/egl_bridge.c b/app_pojavlauncher/src/main/jni/egl_bridge.c index 30b89631bb..8b133a30b2 100644 --- a/app_pojavlauncher/src/main/jni/egl_bridge.c +++ b/app_pojavlauncher/src/main/jni/egl_bridge.c @@ -258,14 +258,18 @@ EXTERNAL_API void* pojavCreateContext(void* contextSrc) { return br_init_context((basic_render_window_t*)contextSrc); } -EXTERNAL_API JNIEXPORT jlong JNICALL -Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(ABI_COMPAT JNIEnv *env, ABI_COMPAT jclass thiz) { - printf("EGLBridge: LWJGL-side Vulkan loader requested the Vulkan handle\n"); - // The code below still uses the env var because +void* maybe_load_vulkan() { + // We use the env var because // 1. it's easier to do that // 2. it won't break if something will try to load vulkan and osmesa simultaneously if(getenv("VULKAN_PTR") == NULL) load_vulkan(); - return strtoul(getenv("VULKAN_PTR"), NULL, 0x10); + return (void*) strtoul(getenv("VULKAN_PTR"), NULL, 0x10); +} + +EXTERNAL_API JNIEXPORT jlong JNICALL +Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(ABI_COMPAT JNIEnv *env, ABI_COMPAT jclass thiz) { + printf("EGLBridge: LWJGL-side Vulkan loader requested the Vulkan handle\n"); + return (jlong) maybe_load_vulkan(); } EXTERNAL_API void pojavSwapInterval(int interval) { diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 951050a093..e1d406fa8c 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -228,47 +228,6 @@ void sendData(int type, int i1, int i2, int i3, int i4) { atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire); } -/** - * Basically a verbatim implementation of ndlopen(), found at - * https://github.com/PojavLauncherTeam/lwjgl3/blob/3.3.1/modules/lwjgl/core/src/generated/c/linux/org_lwjgl_system_linux_DynamicLinkLoader.c#L11 - * The idea is that since, on Android 10 and earlier, the linker doesn't really do namespace nesting. - * It is not a problem as most of the libraries are in the launcher path, but when you try to run - * VulkanMod which loads shaderc outside of the default jni libs directory through this method, - * it can't load it because the path is not in the allowed paths for the anonymous namesapce. - * This method fixes the issue by being in libpojavexec, and thus being in the classloader namespace - */ -jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, - __attribute__((unused)) jclass class, - jlong filename_ptr, - jint jmode) { - const char* filename = (const char*) filename_ptr; - int mode = (int)jmode; - return (jlong) dlopen(filename, mode); -} - -/** - * Install the linker bug mitigation for Android 10 and lower. Fixes VulkanMod crashing on these - * Android versions due to missing namespace nesting. - */ -void installLinkerBugMitigation() { - if(android_get_device_api_level() >= 30) return; - __android_log_print(ANDROID_LOG_INFO, "Api29LinkerFix", "API < 30 detected, installing linker bug mitigation"); - JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; - jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); - if(dynamicLinkLoader == NULL) { - __android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to find the target class"); - (*env)->ExceptionClear(env); - return; - } - JNINativeMethod ndlopenMethod[] = { - {"ndlopen", "(JI)J", &ndlopen_bugfix} - }; - if((*env)->RegisterNatives(env, dynamicLinkLoader, ndlopenMethod, 1) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to register the bugfix method"); - (*env)->ExceptionClear(env); - } -} - /** * This function is meant as a substitute for SharedLibraryUtil.getLibraryPath() that just returns 0 * (thus making the parent Java function return null). This is done to avoid using the LWJGL's default function, diff --git a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c new file mode 100644 index 0000000000..96c6333b47 --- /dev/null +++ b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c @@ -0,0 +1,64 @@ +// +// Created by maks on 06.01.2025. +// + +#include +#include +#include + +#include + +#include +#include +#include + +extern void* maybe_load_vulkan(); + +/** + * Basically a verbatim implementation of ndlopen(), found at + * https://github.com/PojavLauncherTeam/lwjgl3/blob/3.3.1/modules/lwjgl/core/src/generated/c/linux/org_lwjgl_system_linux_DynamicLinkLoader.c#L11 + * but with our own additions for stuff like vulkanmod. + */ +static jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, + __attribute__((unused)) jclass class, + jlong filename_ptr, + jint jmode) { + const char* filename = (const char*) filename_ptr; + + // Oveeride vulkan loading to let us load vulkan ourselves + if(strstr(filename, "libvulkan.so") == filename) { + printf("LWJGL linkerhook: replacing load for libvulkan.so with custom driver\n"); + return (jlong) maybe_load_vulkan(); + } + + // This hook also serves the task of mitigating a bug: the idea is that since, on Android 10 and + // earlier, the linker doesn't really do namespace nesting. + // It is not a problem as most of the libraries are in the launcher path, but when you try to run + // VulkanMod which loads shaderc outside of the default jni libs directory through this method, + // it can't load it because the path is not in the allowed paths for the anonymous namesapce. + // This method fixes the issue by being in libpojavexec, and thus being in the classloader namespace + + int mode = (int)jmode; + return (jlong) dlopen(filename, mode); +} + +/** + * Install the LWJGL dlopen hook. This allows us to mitigate linker bugs and add custom library overrides. + */ +void installLinkerBugMitigation() { + __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "API < 30 detected, installing linker bug mitigation"); + JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; + jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); + if(dynamicLinkLoader == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "LwjglLinkerHook", "Failed to find the target class"); + (*env)->ExceptionClear(env); + return; + } + JNINativeMethod ndlopenMethod[] = { + {"ndlopen", "(JI)J", &ndlopen_bugfix} + }; + if((*env)->RegisterNatives(env, dynamicLinkLoader, ndlopenMethod, 1) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "LwjglLinkerHook", "Failed to register the hooked method"); + (*env)->ExceptionClear(env); + } +} \ No newline at end of file From 208b92be83b5b1202070335a94dacbad13e5bb14 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Wed, 8 Jan 2025 00:00:48 +0300 Subject: [PATCH 15/15] Style[exec_hooks]: change function name, log lines, add clipboard constants --- app_pojavlauncher/src/main/jni/input_bridge_v3.c | 2 +- app_pojavlauncher/src/main/jni/java_exec_hooks.c | 2 +- app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c | 4 ++-- app_pojavlauncher/src/main/jni/utils.h | 6 ++++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index e1d406fa8c..93c1b9041b 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -57,7 +57,7 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { jobject mouseDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_mouseDownBuffer); pojav_environ->mouseDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, mouseDownBufferJ); hookExec(); - installLinkerBugMitigation(); + installLwjglDlopenHook(); installEMUIIteratorMititgation(); } diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/java_exec_hooks.c index 0972b3c73d..4469ba117d 100644 --- a/app_pojavlauncher/src/main/jni/java_exec_hooks.c +++ b/app_pojavlauncher/src/main/jni/java_exec_hooks.c @@ -52,7 +52,7 @@ static jint hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mo if(strcmp(prog_basename, "xdg-open") == 0) { // When invoking xdg-open, send the open URL into Android - Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); + Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, CLIPBOARD_OPEN, argBlock); return 0; }else if(strcmp(prog_basename, "ffmpeg") == 0) { // When invoking ffmpeg, always replace the program path with the path to ffmpeg from the plugin. diff --git a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c index 96c6333b47..8694613d14 100644 --- a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c +++ b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c @@ -45,8 +45,8 @@ static jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, /** * Install the LWJGL dlopen hook. This allows us to mitigate linker bugs and add custom library overrides. */ -void installLinkerBugMitigation() { - __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "API < 30 detected, installing linker bug mitigation"); +void installLwjglDlopenHook() { + __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "Installing LWJGL dlopen() hook"); JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); if(dynamicLinkLoader == NULL) { diff --git a/app_pojavlauncher/src/main/jni/utils.h b/app_pojavlauncher/src/main/jni/utils.h index 4a03c726a2..69583c44eb 100644 --- a/app_pojavlauncher/src/main/jni/utils.h +++ b/app_pojavlauncher/src/main/jni/utils.h @@ -2,7 +2,9 @@ #include - +#define CLIPBOARD_COPY 2000 +#define CLIPBOARD_PASTE 2001 +#define CLIPBOARD_OPEN 2002 char** convert_to_char_array(JNIEnv *env, jobjectArray jstringArray); jobjectArray convert_from_char_array(JNIEnv *env, char **charArray, int num_rows); @@ -10,7 +12,7 @@ void free_char_array(JNIEnv *env, jobjectArray jstringArray, const char **charAr jstring convertStringJVM(JNIEnv* srcEnv, JNIEnv* dstEnv, jstring srcStr); void hookExec(); -void installLinkerBugMitigation(); +void installLwjglDlopenHook(); void installEMUIIteratorMititgation(); JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jbyteArray copySrc);