如何通过 Linux 中的本机代码从 JavaFX 管理任务栏

问题描述 投票:0回答:2

我正在尝试通过 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);
    }
}

有人可以告诉我我做错了什么,也许可以指出我正确的地方 指导如何实施?

非常感谢。 彼得

java javafx taskbar java-ffm
2个回答
6
投票

我无法解决这个问题,但我怀疑这是因为我运行的是 Gnome 43.9 Classic,而且我不确定它是否支持在任务栏按钮中显示进度。 但我至少可以提供一些建议:

  • X中的Window类型不是指针或地址。 它是一个XID,通常是一个32位整数。
  • property
    参数是一个Atom,它也是一个XID,而不是地址或指针。 必须通过将原子的字符串名称传递给XInternAtom来获取原子。
  • 据我所知,Atom 的名称以下划线开头:
    _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 程序设置相同的窗口属性,但这也没有效果。


1
投票

基于 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);
    }
}

Taskbar showcase

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.