Added the widget builder API, also fixed the main API which was broken due to using the JNIEnv across threads
M src/meson.build +4 -1
@@ 2,6 2,7 @@ project('wofij', 'c', 'java', default_op
 cc = meson.get_compiler('c')
 
 wofi = dependency('wofi')
+pixbuf = dependency('gdk-pixbuf-2.0')
 
 java_home = get_option('jdk_home')
 mode_class = get_option('mode_class')

          
@@ 20,16 21,18 @@ srcdir = '../wofi_jni/src'
 
 inc = include_directories('../wofi_jni/inc', java_home + '/include', java_home + '/include/linux')
 
-deps = [wofi, jvm]
+deps = [wofi, jvm, pixbuf]
 
 jsources = [jsrcdir + '/Cache.java',
 			jsrcdir + '/Map.java',
 			jsrcdir + '/Mode.java',
 			jsrcdir + '/Widget.java',
+			jsrcdir + '/WidgetBuilder.java',
 			jsrcdir + '/Wofi.java']
 		
 sources = [srcdir + '/cache_jni.c',
 			srcdir + '/map_jni.c',
+			srcdir + '/widget_builder_jni.c',
 			srcdir + '/wofi_jni.c']
 
 jar(meson.project_name(), jsources)

          
M src/ninja/scoopta/software/wofij/Widget.java +5 -1
@@ 1,5 1,5 @@ 
 /*
- *  Copyright (C) 2020 Scoopta
+ *  Copyright (C) 2020-2022 Scoopta
  *  This file is part of WofiJ
  *  WofiJ is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by

          
@@ 31,4 31,8 @@ public final class Widget {
 		}
 		widget = Wofi.createWidget(wofi.mode, ctext.getBytes(), (searchText + "\0").getBytes(), cactions.getBytes(), actions.length);
 	}
+	
+	Widget(long widget) {
+		this.widget = widget;
+	}
 }

          
A => src/ninja/scoopta/software/wofij/WidgetBuilder.java +68 -0
@@ 0,0 1,68 @@ 
+/*
+ *  Copyright (C) 2022 Scoopta
+ *  This file is part of WofiJ
+ *  WofiJ is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    WofiJ is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with WofiJ.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package ninja.scoopta.software.wofij;
+
+public final class WidgetBuilder {
+	private final long builder;
+	
+	public WidgetBuilder(Wofi wofi, int actions) {
+		builder = init(wofi.mode, actions);
+	}
+	
+	private WidgetBuilder(long builder) {
+		this.builder = builder;
+	}
+	
+	public final void setSearchText(String searchText) {
+		setSearchText(builder, (searchText + "\0").getBytes());
+	}
+	
+	public final void setAction(String action) {
+		setAction(builder, (action + "\0").getBytes());
+	}
+	
+	public final void insertText(String text, String... classes) {
+		String cclasses = "";
+		for(String tmp : classes) {
+			cclasses += tmp + "\0";
+		}
+		insertText(builder, (text + "\0").getBytes(), cclasses.getBytes(), classes.length);
+	}
+
+	public final WidgetBuilder getIdx(int idx) {
+		return new WidgetBuilder(getIdx(builder, idx));
+	}
+	
+	public final Widget getWidget() {
+		return new Widget(getWidget(builder));
+	}
+	
+	private static final native long init(long mode, int actions);
+	
+	private static final native void setSearchText(long builder, byte[] searchText);
+	
+	private static final native void setAction(long builder, byte[] action);
+	
+	private static final native void insertText(long builder, byte[] text, byte[] classes, int classCount);
+	
+	private static final native void insertImage(long builder, long pixbuf, byte[] classes, int classCount);
+	
+	private static final native long getIdx(long builder, int idx);
+	
+	private static final native long getWidget(long builder);
+}

          
A => wofi_jni/inc/widget_builder_jni.h +25 -0
@@ 0,0 1,25 @@ 
+/*
+ *  Copyright (C) 2022 Scoopta
+ *  This file is part of WofiJ
+ *  WofiJ is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    WofiJ is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with WofiJ.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef WIDGET_BUILDER_JNI_H
+#define WIDGET_BUILDER_JNI_H
+
+#include <jni.h>
+
+void widget_builder_jni_init(JNIEnv* env);
+
+#endif

          
A => wofi_jni/src/widget_builder_jni.c +140 -0
@@ 0,0 1,140 @@ 
+/*
+ *  Copyright (C) 2022 Scoopta
+ *  This file is part of WofiJ
+ *  WofiJ is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    WofiJ is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with WofiJ.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <widget_builder_jni.h>
+
+#include <widget_builder_api.h>
+
+static struct widget_builder* init(JNIEnv* env, jclass class, struct mode* mode, jint actions) {
+	(void) env;
+	(void) class;
+	return wofi_widget_builder_init(mode, actions);
+}
+
+static void set_search_text(JNIEnv* env, jclass class, struct widget_builder* builder, jbyteArray arr) {
+	(void) class;
+	char* search_text = (*env)->GetPrimitiveArrayCritical(env, arr, NULL);
+	wofi_widget_builder_set_search_text(builder, search_text);
+	(*env)->ReleasePrimitiveArrayCritical(env, arr, search_text, 0);
+}
+
+static void set_action(JNIEnv* env, jclass class, struct widget_builder* builder, jbyteArray arr) {
+	(void) class;
+	char* action = (*env)->GetPrimitiveArrayCritical(env, arr, NULL);
+	wofi_widget_builder_set_action(builder, action);
+	(*env)->ReleasePrimitiveArrayCritical(env, arr, action, 0);
+}
+
+static void insert_text(JNIEnv* env, jclass class, struct widget_builder* builder, jbyteArray text, jbyteArray classes, jint class_count) {
+	(void) class;
+	char* text_str = (*env)->GetPrimitiveArrayCritical(env, text, NULL);
+	char* class_str = (*env)->GetPrimitiveArrayCritical(env, classes, NULL);
+
+	char* tmp_class = class_str;
+
+	struct wl_list class_list;
+	wl_list_init(&class_list);
+
+	for(ssize_t count = 0; count < class_count; ++count) {
+		struct css_class* node = malloc(sizeof(struct css_class));
+		node->class = tmp_class;
+		tmp_class += strlen(tmp_class) + 1;
+		wl_list_insert(&class_list, &node->link);
+	}
+
+	wofi_widget_builder_insert_text_with_list(builder, text_str, &class_list);
+
+	struct css_class* node, *tmp;
+	wl_list_for_each_safe(node, tmp, &class_list, link) {
+		free(node);
+	}
+
+	(*env)->ReleasePrimitiveArrayCritical(env, classes, class_str, 0);
+	(*env)->ReleasePrimitiveArrayCritical(env, text, text_str, 0);
+}
+
+static void insert_image(JNIEnv* env, jclass class, struct widget_builder* builder, GdkPixbuf* pixbuf, jbyteArray classes, jint class_count) {
+	(void) class;
+	char* class_str = (*env)->GetPrimitiveArrayCritical(env, classes, NULL);
+
+	char* tmp_class = class_str;
+
+	struct wl_list class_list;
+	wl_list_init(&class_list);
+
+	for(ssize_t count = 0; count < class_count; ++count) {
+		struct css_class* node = malloc(sizeof(struct css_class));
+		node->class = tmp_class;
+		tmp_class += strlen(tmp_class) + 1;
+		wl_list_insert(&class_list, &node->link);
+	}
+
+	wofi_widget_builder_insert_image_with_list(builder, pixbuf, &class_list);
+
+	struct css_class* node, *tmp;
+	wl_list_for_each_safe(node, tmp, &class_list, link) {
+		free(node);
+	}
+
+	(*env)->ReleasePrimitiveArrayCritical(env, classes, class_str, 0);
+}
+
+static struct widget_builder* get_idx(JNIEnv* env, jclass class, struct widget_builder* builder, jint idx) {
+	(void) env;
+	(void) class;
+	return wofi_widget_builder_get_idx(builder, idx);
+}
+
+static struct widget* get_widget(JNIEnv* env, jclass class, struct widget_builder* builder) {
+	(void) env;
+	(void) class;
+	return wofi_widget_builder_get_widget(builder);
+}
+
+void widget_builder_jni_init(JNIEnv* env) {
+	JNINativeMethod natives[7];
+	natives[0].name = "init";
+	natives[0].signature = "(JI)J";
+	natives[0].fnPtr = init;
+
+	natives[1].name = "setSearchText";
+	natives[1].signature = "(J[B)V";
+	natives[1].fnPtr = set_search_text;
+
+	natives[2].name = "setAction";
+	natives[2].signature = "(J[B)V";
+	natives[2].fnPtr = set_action;
+
+	natives[3].name = "insertText";
+	natives[3].signature = "(J[B[BI)V";
+	natives[3].fnPtr = insert_text;
+
+	natives[4].name = "insertImage";
+	natives[4].signature = "(JJ[BI)V";
+	natives[4].fnPtr = insert_image;
+
+	natives[5].name = "getIdx";
+	natives[5].signature = "(JI)J";
+	natives[5].fnPtr = get_idx;
+
+	natives[6].name = "getWidget";
+	natives[6].signature = "(J)J";
+	natives[6].fnPtr = get_widget;
+
+	jclass widget_builder = (*env)->FindClass(env, "ninja/scoopta/software/wofij/WidgetBuilder");
+	(*env)->RegisterNatives(env, widget_builder, natives, sizeof(natives) / sizeof(JNINativeMethod));
+}

          
M wofi_jni/src/wofi_jni.c +16 -9
@@ 1,5 1,5 @@ 
 /*
- *  Copyright (C) 2020 Scoopta
+ *  Copyright (C) 2020-2022 Scoopta
  *  This file is part of WofiJ
  *  WofiJ is free software: you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by

          
@@ 21,6 21,7 @@ 
 
 #include <map_jni.h>
 #include <cache_jni.h>
+#include <widget_builder_jni.h>
 
 #include <map.h>
 #include <utils.h>

          
@@ 29,8 30,10 @@ 
 #include <jni.h>
 
 static struct mode* mode;
-static JNIEnv* env;
+static JavaVM* jvm;
+static JNIEnv* env, *gtk_env;
 static jclass wofi;
+static bool on_gtk_thread = false;
 
 static jbyteArray parse_image_escapes(JNIEnv* env, jclass class, jbyteArray arr) {
 	(void) class;

          
@@ 151,7 154,6 @@ void init(struct mode* _mode, struct map
 
 void load(struct mode* _mode) {
 	mode = _mode;
-	JavaVM* jvm;
 	JavaVMInitArgs args;
 	JavaVMOption options[1];
 #ifdef CLASS_PATH

          
@@ 234,6 236,7 @@ void load(struct mode* _mode) {
 
 	map_jni_init(env);
 	cache_jni_init(env);
+	widget_builder_jni_init(env);
 }
 
 size_t get_arg_count(void) {

          
@@ 272,14 275,18 @@ bool no_entry(void) {
 }
 
 struct widget* get_widget(void) {
-	jmethodID method = (*env)->GetStaticMethodID(env, wofi, "getWidget", "()J");
-	return (struct widget*) (*env)->CallStaticLongMethod(env, wofi, method);
+	if(!on_gtk_thread) {
+		on_gtk_thread = true;
+		(*jvm)->AttachCurrentThread(jvm, (void**) &gtk_env, NULL);
+	}
+	jmethodID method = (*gtk_env)->GetStaticMethodID(gtk_env, wofi, "getWidget", "()J");
+	return (struct widget*) (*gtk_env)->CallStaticLongMethod(gtk_env, wofi, method);
 }
 
 void exec(const char* cmd) {
-	jmethodID method = (*env)->GetStaticMethodID(env, wofi, "exec", "([B)V");
+	jmethodID method = (*gtk_env)->GetStaticMethodID(gtk_env, wofi, "exec", "([B)V");
 	size_t cmd_l = strlen(cmd);
-	jbyteArray arr = (*env)->NewByteArray(env, cmd_l);
-	(*env)->SetByteArrayRegion(env, arr, 0, cmd_l, (jbyte*) cmd);
-	(*env)->CallStaticVoidMethod(env, wofi, method, arr);
+	jbyteArray arr = (*gtk_env)->NewByteArray(gtk_env, cmd_l);
+	(*gtk_env)->SetByteArrayRegion(gtk_env, arr, 0, cmd_l, (jbyte*) cmd);
+	(*gtk_env)->CallStaticVoidMethod(gtk_env, wofi, method, arr);
 }