api-impl-jni: add workarounds for Gtk's non-graceful handling of layout changes during the snapshot phase

This commit is contained in:
Mis012
2025-06-20 22:36:09 +02:00
parent 3a3366abfa
commit 6031eecefc
7 changed files with 120 additions and 12 deletions

View File

@@ -65,7 +65,7 @@ static void bind_listitem_cb(GtkListItemFactory *factory, GtkListItem *list_item
GtkWidget *label = gtk_list_item_get_child(list_item); GtkWidget *label = gtk_list_item_get_child(list_item);
ListEntry *entry = gtk_list_item_get_item(list_item); ListEntry *entry = gtk_list_item_get_item(list_item);
gtk_label_set_text(GTK_LABEL(label), entry->text); atl_safe_gtk_label_set_text(GTK_LABEL(label), entry->text);
} }
static void activate_cb(GtkListView *list, guint position, struct click_callback_data *d) static void activate_cb(GtkListView *list, guint position, struct click_callback_data *d)

View File

@@ -2,6 +2,8 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <pthread.h> #include <pthread.h>
#include <gtk/gtk.h>
#include "util.h" #include "util.h"
#include "src/api-impl-jni/defines.h" #include "src/api-impl-jni/defines.h"
@@ -279,3 +281,92 @@ int get_nio_buffer_size(JNIEnv *env, jobject buffer)
return limit - position; return limit - position;
} }
/* Calling these functions while snapshotting will cause Gtk to not snapshot the affected widgets.
* Below are "safe" wrappers which will postpone the calls if inside a snapshot.
* Specifically, gtk_widget_add_tick_callback will make sure the calls are made in the next
* Update phase. */
/* callbacks */
static gboolean queue_set_text(GtkWidget *label, GdkFrameClock *frame_clock, gpointer str)
{
gtk_label_set_text(GTK_LABEL(label), str);
/* we always call strdup so we always want to free */
free(str);
return G_SOURCE_REMOVE;
}
static gboolean queue_queue_allocate(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data)
{
gtk_widget_queue_allocate(widget);
return G_SOURCE_REMOVE;
}
static gboolean queue_queue_resize(GtkWidget *widget, GdkFrameClock *frame_clock, gpointer user_data)
{
gtk_widget_queue_resize(widget);
return G_SOURCE_REMOVE;
}
/* Some functions call gtk_widget_queue_allocate or similar internally.
* To prevent that from breaking the snapshotting process, when called at the wrong time,
* we have to follow those functions with this pile of hacks that will unset the problematic flags. */
extern int snapshot_in_progress;
void atl_ensure_widget_snapshotability(GtkWidget *widget)
{
if(snapshot_in_progress) {
GtkAllocation allocation;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
/* we probably don't need to use this deprecated function but it sure is convenient */
gtk_widget_get_allocation(widget, &allocation);
G_GNUC_END_IGNORE_DEPRECATIONS
/* this clears resize request, which seems to be necessary in some cases */
gtk_widget_get_request_mode(widget);
gtk_widget_size_allocate(widget, &allocation, gtk_widget_get_baseline(widget));
gtk_widget_add_tick_callback(widget, queue_queue_allocate, NULL, NULL);
/* the problematic flags get set all the way up the hierarchy */
GtkWidget *parent = gtk_widget_get_parent(widget);
if (parent) {
atl_ensure_widget_snapshotability(parent);
}
}
}
void atl_safe_gtk_label_set_text(GtkLabel* label, const char* str)
{
if(!snapshot_in_progress) {
gtk_label_set_text(label, str);
} else {
/* strdup since the string may not exist by the time the callback runs */
gtk_widget_add_tick_callback(GTK_WIDGET(label), queue_set_text, (gpointer)strdup(str), NULL);
}
}
void atl_safe_gtk_widget_set_visible(GtkWidget *widget, gboolean visible)
{
gtk_widget_set_visible(widget, visible);
GtkWidget *parent = gtk_widget_get_parent(widget);
if (parent) {
atl_ensure_widget_snapshotability(parent);
}
}
void atl_safe_gtk_widget_queue_allocate(GtkWidget *widget)
{
if(!snapshot_in_progress) {
gtk_widget_queue_allocate(widget);
} else {
gtk_widget_add_tick_callback(widget, queue_queue_allocate, NULL, NULL);
}
}
void atl_safe_gtk_widget_queue_resize(GtkWidget *widget)
{
if(!snapshot_in_progress) {
gtk_widget_queue_resize(widget);
} else {
gtk_widget_add_tick_callback(widget, queue_queue_resize, NULL, NULL);
}
}

View File

@@ -1,6 +1,8 @@
#ifndef _UTILS_H_ #ifndef _UTILS_H_
#define _UTILS_H_ #define _UTILS_H_
#include <gtk/gtk.h>
#include <jni.h> #include <jni.h>
#include "defines.h" #include "defines.h"
@@ -186,4 +188,10 @@ void *get_nio_buffer(JNIEnv *env, jobject buffer, jarray *array_ref, jbyte **arr
void release_nio_buffer(JNIEnv *env, jarray array_ref, jbyte *array); void release_nio_buffer(JNIEnv *env, jarray array_ref, jbyte *array);
int get_nio_buffer_size(JNIEnv *env, jobject buffer); int get_nio_buffer_size(JNIEnv *env, jobject buffer);
void atl_ensure_widget_snapshotability(GtkWidget *widget);
void atl_safe_gtk_label_set_text(GtkLabel* label, const char* str);
void atl_safe_gtk_widget_set_visible(GtkWidget *widget, gboolean visible);
void atl_safe_gtk_widget_queue_allocate(GtkWidget *widget);
void atl_safe_gtk_widget_queue_resize(GtkWidget *widget);
#endif #endif

View File

@@ -446,6 +446,8 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1setLayoutParams(JNIEnv *en
android_layout_set_params(ATL_ANDROID_LAYOUT(layout_manager), width, height); android_layout_set_params(ATL_ANDROID_LAYOUT(layout_manager), width, height);
wrapper_widget_set_layout_params(WRAPPER_WIDGET(widget), width, height); wrapper_widget_set_layout_params(WRAPPER_WIDGET(widget), width, height);
atl_ensure_widget_snapshotability(widget);
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
@@ -473,7 +475,7 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1setPadding(JNIEnv *env, jo
JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility(JNIEnv *env, jobject this, jlong widget_ptr, jint visibility, jfloat alpha) { JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility(JNIEnv *env, jobject this, jlong widget_ptr, jint visibility, jfloat alpha) {
GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr))); GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(_PTR(widget_ptr)));
gtk_widget_set_visible(widget, visibility != android_view_View_GONE); atl_safe_gtk_widget_set_visible(widget, visibility != android_view_View_GONE);
gtk_widget_set_opacity(widget, (visibility != android_view_View_INVISIBLE) * alpha); gtk_widget_set_opacity(widget, (visibility != android_view_View_INVISIBLE) * alpha);
gtk_widget_set_sensitive(widget, visibility != android_view_View_INVISIBLE && alpha != 0.0f); gtk_widget_set_sensitive(widget, visibility != android_view_View_INVISIBLE && alpha != 0.0f);
} }
@@ -481,7 +483,7 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1setVisibility(JNIEnv *env,
/** JavaWidget: /** JavaWidget:
* Minimal gtk widget class which does nothing. * Minimal gtk widget class which does nothing.
* Drawing will be overwritten by WrapperWidget. * Drawing will be overwritten by WrapperWidget.
* If it holds children, they will be layouted by AndroidLayout * If it holds children, they will be laid out by AndroidLayout
*/ */
struct _JavaWidget {GtkWidget parent_instance;}; struct _JavaWidget {GtkWidget parent_instance;};
G_DECLARE_FINAL_TYPE(JavaWidget, java_widget, JAVA, WIDGET, GtkWidget) G_DECLARE_FINAL_TYPE(JavaWidget, java_widget, JAVA, WIDGET, GtkWidget)
@@ -595,7 +597,7 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1layout(JNIEnv *env, jobjec
wrapper->real_width = width; wrapper->real_width = width;
wrapper->real_height = height; wrapper->real_height = height;
if (!wrapper->needs_allocation) if (!wrapper->needs_allocation)
gtk_widget_queue_allocate(widget); atl_safe_gtk_widget_queue_allocate(widget);
} }
if (wrapper->needs_allocation) { if (wrapper->needs_allocation) {
allocation.width = width; allocation.width = width;
@@ -607,7 +609,7 @@ JNIEXPORT void JNICALL Java_android_view_View_native_1layout(JNIEnv *env, jobjec
JNIEXPORT void JNICALL Java_android_view_View_native_1requestLayout(JNIEnv *env, jobject this, jlong widget_ptr) { JNIEXPORT void JNICALL Java_android_view_View_native_1requestLayout(JNIEnv *env, jobject this, jlong widget_ptr) {
GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr)); GtkWidget *widget = GTK_WIDGET(_PTR(widget_ptr));
gtk_widget_queue_resize(widget); atl_safe_gtk_widget_queue_resize(widget);
} }
/* we kinda need per-widget css */ /* we kinda need per-widget css */
@@ -711,7 +713,7 @@ JNIEXPORT jboolean JNICALL Java_android_view_View_native_1getMatrix(JNIEnv *env,
JNIEXPORT void JNICALL Java_android_view_View_native_1queueAllocate(JNIEnv *env, jobject this, jlong widget_ptr) JNIEXPORT void JNICALL Java_android_view_View_native_1queueAllocate(JNIEnv *env, jobject this, jlong widget_ptr)
{ {
gtk_widget_queue_allocate(GTK_WIDGET(_PTR(widget_ptr))); atl_safe_gtk_widget_queue_allocate(GTK_WIDGET(_PTR(widget_ptr)));
} }
JNIEXPORT void JNICALL Java_android_view_View_native_1drawBackground(JNIEnv *env, jobject this, jlong widget_ptr, jlong snapshot_ptr) JNIEXPORT void JNICALL Java_android_view_View_native_1drawBackground(JNIEnv *env, jobject this, jlong widget_ptr, jlong snapshot_ptr)

View File

@@ -168,7 +168,7 @@ void wrapper_widget_allocate(GtkWidget *widget, int width, int height, int basel
layout->real_width = width; layout->real_width = width;
layout->real_height = height; layout->real_height = height;
if (!layout->needs_allocation) if (!layout->needs_allocation)
gtk_widget_queue_allocate(wrapper->child); atl_safe_gtk_widget_queue_allocate(wrapper->child);
} }
if (layout->needs_allocation) if (layout->needs_allocation)
gtk_widget_size_allocate(wrapper->child, &allocation, baseline); gtk_widget_size_allocate(wrapper->child, &allocation, baseline);
@@ -181,8 +181,12 @@ void wrapper_widget_allocate(GtkWidget *widget, int width, int height, int basel
gtk_widget_size_allocate(wrapper->background, &allocation, baseline); gtk_widget_size_allocate(wrapper->background, &allocation, baseline);
} }
/* this is used to avoid queing layout changes in the middle of snapshotting */
int snapshot_in_progress = 0;
static void wrapper_widget_snapshot(GtkWidget *widget, GdkSnapshot *snapshot) static void wrapper_widget_snapshot(GtkWidget *widget, GdkSnapshot *snapshot)
{ {
snapshot_in_progress++;
WrapperWidget *wrapper = WRAPPER_WIDGET(widget); WrapperWidget *wrapper = WRAPPER_WIDGET(widget);
if (wrapper->real_height > 0 && wrapper->real_width > 0) { if (wrapper->real_height > 0 && wrapper->real_width > 0) {
gtk_snapshot_push_clip(snapshot, &GRAPHENE_RECT_INIT(0, 0, wrapper->real_width, wrapper->real_height)); gtk_snapshot_push_clip(snapshot, &GRAPHENE_RECT_INIT(0, 0, wrapper->real_width, wrapper->real_height));
@@ -204,6 +208,8 @@ static void wrapper_widget_snapshot(GtkWidget *widget, GdkSnapshot *snapshot)
if (wrapper->real_height > 0 && wrapper->real_width > 0) { if (wrapper->real_height > 0 && wrapper->real_width > 0) {
gtk_snapshot_pop(snapshot); gtk_snapshot_pop(snapshot);
} }
snapshot_in_progress--;
} }
static void wrapper_widget_class_init(WrapperWidgetClass *class) static void wrapper_widget_class_init(WrapperWidgetClass *class)
@@ -257,8 +263,9 @@ void wrapper_widget_queue_draw(WrapperWidget *wrapper)
if(wrapper->child) if(wrapper->child)
gtk_widget_queue_draw(wrapper->child); gtk_widget_queue_draw(wrapper->child);
if (wrapper->computeScroll_method) if (wrapper->computeScroll_method) {
gtk_widget_queue_allocate(GTK_WIDGET(wrapper)); atl_safe_gtk_widget_queue_allocate(GTK_WIDGET(wrapper));
}
} }
static bool on_click(GtkGestureClick *gesture, int n_press, double x, double y, jobject this) static bool on_click(GtkGestureClick *gesture, int n_press, double x, double y, jobject this)

View File

@@ -36,7 +36,7 @@ JNIEXPORT void JNICALL Java_android_widget_Button_native_1setText(JNIEnv *env, j
GtkButton *button = GTK_BUTTON(_PTR(widget_ptr)); GtkButton *button = GTK_BUTTON(_PTR(widget_ptr));
const char *nativeText = ((*env)->GetStringUTFChars(env, text, NULL)); const char *nativeText = ((*env)->GetStringUTFChars(env, text, NULL));
gtk_label_set_text(box_get_label(env, gtk_button_get_child(button)), nativeText); atl_safe_gtk_label_set_text(box_get_label(env, gtk_button_get_child(button)), nativeText);
((*env)->ReleaseStringUTFChars(env, text, nativeText)); ((*env)->ReleaseStringUTFChars(env, text, nativeText));
} }

View File

@@ -43,7 +43,7 @@ JNIEXPORT jlong JNICALL Java_android_widget_TextView_native_1constructor(JNIEnv
JNIEXPORT void JNICALL Java_android_widget_TextView_native_1setText(JNIEnv *env, jobject this, jobject charseq) JNIEXPORT void JNICALL Java_android_widget_TextView_native_1setText(JNIEnv *env, jobject this, jobject charseq)
{ {
const char *text = charseq ? (*env)->GetStringUTFChars(env, charseq, NULL) : NULL; const char *text = charseq ? (*env)->GetStringUTFChars(env, charseq, NULL) : NULL;
gtk_label_set_text(box_get_label(env, _PTR(_GET_LONG_FIELD(this, "widget"))), text ?: ""); atl_safe_gtk_label_set_text(box_get_label(env, _PTR(_GET_LONG_FIELD(this, "widget"))), text ?: "");
if(text) if(text)
(*env)->ReleaseStringUTFChars(env, charseq, text); (*env)->ReleaseStringUTFChars(env, charseq, text);
} }