我正在尝试通过 Java 22+ 中新的 Foreign Function & Memory API 获得更多经验。最好的办法 如何学习新的 API 就是在项目中使用它。
我的项目的目标是在任务栏上报告某些长时间运行的任务的进度。 据我所知,JavaFX 中没有对此的“本机”支持。 有一些图书馆像 FXTaskbarProgressBar 服务于 目的,但仅适用于 Windows 操作系统。它使用“旧”Java 本机接口 (JNI)。
经过短暂的研究,我发现了一个简单的 Go 库 任务栏。这个库激励我尝试将 JavaFX 移植到 Java。
首先,我使用
jextract
来获取 Java 绑定到本机库调用:
jextract --output target/generated-sources/jextract -t "taskbar_test.gen" --include-function "XOpenDisplay" --include-function "XChangeProperty" --include-function "XFlush" --include-function "XCloseDisplay" /usr/include/X11/Xlib.h
然后我创建了一个简单的应用程序来模拟长时间运行的进程 我尝试通过调用方法更新任务栏上的进度 我在X11的文档中找到了“XChangeProperty”: https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html#XChangeProperty
不幸的是,这不起作用。程序不会崩溃, 任务正在后台运行,但任务栏上没有发生更新。
这是我创建的代码:
package taskbar_test;
import com.sun.glass.ui.Window;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import taskbar_test.gen.Xlib_h;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
public class AppLinuxXlib extends Application {
@Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start Long Running Task");
startButton.setOnAction(event -> {
final long rawHandle = Window.getWindows().getFirst().getRawHandle();
System.out.println(rawHandle);
// Create a long-running task
Task<Void> longTask = new Task<>() {
@Override
protected Void call() throws Exception {
System.out.println("Started");
try (var arena = Arena.ofConfined()) {
var NET_WM_XAPP_PROGRESS = arena.allocateFrom("NET_WM_XAPP_PROGRESS");
// var NET_WM_XAPP_PROGRESS_PULSE = arena.allocateFrom("NET_WM_XAPP_PROGRESS_PULSE");
MemorySegment x11Session = Xlib_h.XOpenDisplay(MemorySegment.NULL);
System.out.println(x11Session);
// Prepare the progress data
MemorySegment initData = arena.allocateFrom(ValueLayout.JAVA_INT, 0);
Xlib_h.XChangeProperty(x11Session, // display
MemorySegment.ofAddress(rawHandle).address(), // window
NET_WM_XAPP_PROGRESS.address(), // property
6, // type
32, // format
0, // mode PropModeReplace=0
initData, // data
1); // nelements
Xlib_h.XFlush(x11Session);
System.out.println("Countdown started");
// Set the taskbar progress
for (int i = 0; i <= 100; i+=20) {
// Simulate work
Thread.sleep(500);
System.out.println(i);
MemorySegment progressData = arena.allocateFrom(ValueLayout.JAVA_INT, i);
// Update taskbar progress
// https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html#XChangeProperty
Xlib_h.XChangeProperty(x11Session, // display
MemorySegment.ofAddress(rawHandle).address(), // window
NET_WM_XAPP_PROGRESS.address(), // property
6, // type
32, // format
0, // mode PropModeReplace=0
progressData, // data
1); // nelements
Xlib_h.XFlush(x11Session);
}
System.out.println("Finished");
Xlib_h.XCloseDisplay(x11Session);
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
};
// Start the task in a new thread
new Thread(longTask).start();
});
VBox vbox = new VBox(10, startButton);
Scene scene = new Scene(vbox, 300, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Taskbar Progress Example Linux");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
有人可以告诉我我做错了什么,也许可以指出我正确的地方 指导如何实施?
非常感谢。 彼得
我无法解决这个问题,但我怀疑这是因为我运行的是 Gnome 43.9 Classic,而且我不确定它是否支持在任务栏按钮中显示进度。 但我至少可以提供一些建议:
property
参数是一个Atom,它也是一个XID,而不是地址或指针。 必须通过将原子的字符串名称传递给XInternAtom来获取原子。_NET_WM_XAPP_PROGRESS
(我不明白为什么网络上关于此属性的文档如此之少。)考虑到这些事情,我将你的代码更改为这样。 (我用对 java.lang.foreign 的显式调用替换了 taskbar_test.gen.Xlib_h 包。)
import com.sun.glass.ui.Window;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.lang.invoke.MethodHandle;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.foreign.Linker;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.FunctionDescriptor;
public class AppLinuxXlib extends Application {
private static final int None = 0; // from X.h
private static final int False = 0; // from Xlib.h
private static final int PropModeReplace = 0; // from X.h
private static final int XA_CARDINAL = 6; // from Xtom.h
private MethodHandle XOpenDisplay;
private MethodHandle XCloseDisplay;
private MethodHandle XInternAtom;
private MethodHandle XChangeProperty;
private MethodHandle XFlush;
@Override
public void init()
throws Exception {
Linker linker = Linker.nativeLinker();
SymbolLookup lookup = SymbolLookup.loaderLookup();
XOpenDisplay = linker.downcallHandle(
lookup.findOrThrow("XOpenDisplay"),
FunctionDescriptor.of(
ValueLayout.ADDRESS, // returns Display *
ValueLayout.ADDRESS.withName("display_name") // char *display_name
));
XCloseDisplay = linker.downcallHandle(
lookup.findOrThrow("XCloseDisplay"),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns int
ValueLayout.ADDRESS.withName("display") // Display *display
));
XInternAtom = linker.downcallHandle(
lookup.findOrThrow("XInternAtom"),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns Atom
ValueLayout.ADDRESS.withName("display"), // Display *display
ValueLayout.ADDRESS.withName("atom_name"), // char *atom_name
ValueLayout.JAVA_INT.withName("only_if_exists") // Bool only_if_exists
));
XChangeProperty = linker.downcallHandle(
lookup.findOrThrow("XChangeProperty"),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns int
ValueLayout.ADDRESS.withName("display"), // Display *display
ValueLayout.JAVA_INT.withName("w"), // Window w
ValueLayout.JAVA_INT.withName("property"), // Atom property
ValueLayout.JAVA_INT.withName("type"), // Atom type
ValueLayout.JAVA_INT.withName("format"), // int format
ValueLayout.JAVA_INT.withName("mode"), // int mode
ValueLayout.ADDRESS.withName("data"), // char *data
ValueLayout.JAVA_INT.withName("nelements") // int nelements
));
XFlush = linker.downcallHandle(
lookup.findOrThrow("XFlush"),
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns int
ValueLayout.ADDRESS.withName("display") // Display *display
));
}
private MemorySegment XOpenDisplay(MemorySegment display)
throws Throwable {
return (MemorySegment) XOpenDisplay.invokeExact(display);
}
private int XCloseDisplay(MemorySegment display)
throws Throwable {
return (int) XCloseDisplay.invokeExact(display);
}
private int XInternAtom(MemorySegment display,
MemorySegment atomName,
int onlyIfExists)
throws Throwable {
return (int) XInternAtom.invokeExact(display, atomName, onlyIfExists);
}
private int XFlush(MemorySegment display)
throws Throwable {
return (int) XFlush.invokeExact(display);
}
private int XChangeProperty(MemorySegment display,
int window,
int property,
int type,
int format,
int mode,
MemorySegment data,
int dataLen)
throws Throwable {
return (int) XChangeProperty.invokeExact(display,
window, property, type, format, mode, data, dataLen);
}
@Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start Long Running Task");
startButton.setOnAction(event -> {
final int window = (int) Window.getWindows().getFirst().getNativeWindow();
System.out.printf("Window=%#x%n", window);
// Create a long-running task
Task<Void> longTask = new Task<>() {
@Override
protected Void call() throws Exception {
System.out.println("Started");
try (var arena = Arena.ofConfined()) {
MemorySegment x11Session = XOpenDisplay(MemorySegment.NULL);
System.out.println("display=" + x11Session);
var name = arena.allocateFrom("_NET_WM_XAPP_PROGRESS");
var NET_WM_XAPP_PROGRESS = XInternAtom(x11Session, name, 0);
if (NET_WM_XAPP_PROGRESS == None) {
throw new RuntimeException("XInternAtom failed.");
}
// var NET_WM_XAPP_PROGRESS_PULSE = arena.allocateFrom("NET_WM_XAPP_PROGRESS_PULSE");
// Prepare the progress data
MemorySegment progressData = arena.allocateFrom(ValueLayout.JAVA_INT, 0);
int status = XChangeProperty(
x11Session, // display
window, // window
NET_WM_XAPP_PROGRESS, // property
XA_CARDINAL, // type
32, // format
PropModeReplace, // mode
progressData, // data
1); // nelements
if (status == False) {
throw new RuntimeException("XChangeProperty returned " + status);
}
status = XFlush(x11Session);
if (status == False) {
throw new RuntimeException("XFlush returned " + status);
}
System.out.println("Countdown started");
// Set the taskbar progress
for (int i = 0; i <= 100; i+=20) {
// Simulate work
Thread.sleep(500);
System.out.println(i);
progressData.set(ValueLayout.JAVA_INT, 0, i);
// Update taskbar progress
// https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html#XChangeProperty
status = XChangeProperty(
x11Session, // display
window, // window
NET_WM_XAPP_PROGRESS, // property
XA_CARDINAL, // type
32, // format
PropModeReplace, // mode
progressData, // data
1); // nelements
if (status == False) {
throw new RuntimeException("XChangeProperty returned " + status);
}
status = XFlush(x11Session);
if (status == False) {
throw new RuntimeException("XFlush returned " + status);
}
}
System.out.println("Finished");
XCloseDisplay(x11Session);
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
};
// Start the task in a new thread
new Thread(longTask).start();
});
VBox vbox = new VBox(10, startButton);
Scene scene = new Scene(vbox, 300, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Taskbar Progress Example Linux");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
它可以运行,但对我的系统没有视觉效果。 不知道是不是因为我的 Gnome 版本不支持。 我还尝试使用 xprop 程序设置相同的窗口属性,但这也没有效果。
基于 VGR 发布的解决方案,我想展示使用
jextract
生成的代码的更新代码。
首先这是我使用的命令:
jextract --output target/generated-sources/jextract -t "taskbar_test.gen" --include-function "XOpenDisplay" --include-function "XChangeProperty" --include-function "XFlush" --include-function "XCloseDisplay" --include-function "XInternAtom" --include-constant "PropModeReplace" --include-constant "False" --include-constant "None" /usr/include/X11/Xlib.h
有必要再添加一种方法 (
XInternAtom
),并且我还找到了 False
和 None
的常量,因此它们也包含在内。
最终代码为:
package taskbar_test;
import com.sun.glass.ui.Window;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import static taskbar_test.gen.Xlib_h.*;
public class AppLinuxXlib extends Application {
private static final int XA_CARDINAL = 6; // from Xtom.h
@Override
public void start(Stage primaryStage) {
Button startButton = new Button("Start Long Running Task");
startButton.setOnAction(_ -> {
final long window = Window.getWindows().getFirst().getNativeWindow();
System.out.printf("Window=%#x%n", window);
// Create a long-running task
Task<Void> longTask = new Task<>() {
@Override
protected Void call() throws Exception {
System.out.println("Started");
try (var arena = Arena.ofConfined()) {
MemorySegment x11Session = XOpenDisplay(MemorySegment.NULL);
System.out.println("display=" + x11Session);
var name = arena.allocateFrom("_NET_WM_XAPP_PROGRESS");
var NET_WM_XAPP_PROGRESS = XInternAtom(x11Session, name, 0);
if (NET_WM_XAPP_PROGRESS == None()) {
throw new RuntimeException("XIntermAtom failed.");
}
// Prepare the progress data
MemorySegment initData = arena.allocateFrom(ValueLayout.JAVA_INT, 0);
int status = XChangeProperty(
x11Session, // display
window, // window
NET_WM_XAPP_PROGRESS, // property
XA_CARDINAL, // type
32, // format
PropModeReplace(), // mode PropModeReplace=0
initData, // data
1); // nelements
if (status == False()) {
throw new RuntimeException("XChangeProperty returned " + status);
}
status = XFlush(x11Session);
if (status == False()) {
throw new RuntimeException("XFlush returned " + status);
}
// Set the taskbar progress
for (int i = 0; i <= 100; i+=20) {
// Simulate work
Thread.sleep(500);
System.out.println(i);
MemorySegment progressData = arena.allocateFrom(ValueLayout.JAVA_INT, i);
// Update taskbar progress
// https://www.x.org/releases/X11R7.7/doc/libX11/libX11/libX11.html#XChangeProperty
status = XChangeProperty(
x11Session, // display
window, // window
NET_WM_XAPP_PROGRESS, // property
XA_CARDINAL, // type
32, // format
PropModeReplace(), // mode PropModeReplace=0
progressData, // data
1); // nelements
if (status == False()) {
throw new RuntimeException("XChangeProperty returned " + status);
}
status = XFlush(x11Session);
if (status == False()) {
throw new RuntimeException("XFlush returned " + status);
}
}
System.out.println("Finished");
XCloseDisplay(x11Session);
} catch (Throwable ex) {
ex.printStackTrace();
}
return null;
}
};
// Start the task in a new thread
new Thread(longTask).start();
});
VBox vbox = new VBox(10, startButton);
Scene scene = new Scene(vbox, 300, 200);
primaryStage.setScene(scene);
primaryStage.setTitle("Taskbar Progress Example Linux");
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}