You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Linux原生代码在JavaFX中实现任务栏进度更新

问题描述

我想通过项目实践掌握Java 22+的Foreign Function & Memory API,计划实现一个JavaFX程序,在Linux任务栏显示长耗时任务的进度。目前JavaFX无原生支持,现有类库如FXTaskbarProgressBar仅支持Windows且基于JNI。

参考Go语言的taskbar库,我用jextract生成Xlib的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

随后编写了模拟长任务并调用XChangeProperty更新任务栏进度的JavaFX代码,但程序运行后任务栏无进度更新,程序未崩溃。代码如下:

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

请问我的代码存在什么问题,正确的实现方向是什么?


问题分析与解决方案

你的代码存在的核心问题

  1. 属性名传递错误
    Xlib要求窗口属性名必须是原子值(Atom),不是字符串的内存指针。你直接把NET_WM_XAPP_PROGRESS字符串的内存地址传给XChangeProperty,桌面环境无法识别这个属性。必须先用XInternAtom将字符串转换为对应的Atom值。

  2. 窗口ID参数传递错误
    XChangeProperty的第二个参数是窗口ID(无符号长整型),但你传递的是MemorySegment.ofAddress(rawHandle).address()——这相当于把窗口ID的内存地址当成了窗口ID本身,完全不符合Xlib的参数要求,应该直接传递rawHandle对应的MemorySegment(或直接传数值,取决于jextract生成的绑定类型)。

  3. 属性类型参数硬编码错误
    你给type参数传了6,这是无意义的硬编码。对于NET_WM_XAPP_PROGRESS这类进度属性,类型应该是XA_CARDINAL(需要用XInternAtom获取对应的原子值)。

  4. 缺少进度条显示开关属性
    仅设置NET_WM_XAPP_PROGRESS进度值没用,必须同时设置NET_WM_XAPP_PROGRESS_VISIBLE为1,桌面环境才会渲染任务栏进度条;任务结束后还要把这个属性设为0,隐藏进度条。

正确的实现方向

  1. 补充Xlib函数绑定
    修改jextract命令,添加XInternAtom函数的绑定,因为转换字符串到原子值必须用这个函数:

    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" /usr/include/X11/Xlib.h
    
  2. 修正参数与属性处理逻辑

    • 用XInternAtom获取NET_WM_XAPP_PROGRESS、NET_WM_XAPP_PROGRESS_VISIBLE、XA_CARDINAL对应的原子值;
    • 正确传递窗口ID:用MemorySegment.ofAddress(rawHandle)作为窗口参数(匹配Xlib的Window类型要求);
    • 将type参数替换为XA_CARDINAL的原子值。
  3. 完善进度条生命周期管理

    • 任务启动时,先设置NET_WM_XAPP_PROGRESS_VISIBLE为1,再设置初始进度;
    • 任务执行过程中同步更新进度值;
    • 任务结束后,设置NET_WM_XAPP_PROGRESS_VISIBLE为0,清理进度条。
  4. 线程安全注意
    X11连接通常是单线程上下文,确保所有Xlib调用都在同一个后台线程中执行,避免跨线程操作X11连接导致的异常。

修正后的核心代码片段

try (var arena = Arena.ofConfined()) {
    MemorySegment x11Session = Xlib_h.XOpenDisplay(MemorySegment.NULL);
    if (x11Session.address() == 0) {
        throw new RuntimeException("Failed to open X11 display");
    }

    // 获取必要的原子值
    var netWmXappProgress = Xlib_h.XInternAtom(x11Session, arena.allocateFrom("NET_WM_XAPP_PROGRESS"), false);
    var netWmXappProgressVisible = Xlib_h.XInternAtom(x11Session, arena.allocateFrom("NET_WM_XAPP_PROGRESS_VISIBLE"), false);
    var xaCardinal = Xlib_h.XInternAtom(x11Session, arena.allocateFrom("CARDINAL"), false);

    // 显示进度条
    MemorySegment visibleData = arena.allocateFrom(ValueLayout.JAVA_INT, 1);
    Xlib_h.XChangeProperty(x11Session,
            MemorySegment.ofAddress(rawHandle),
            netWmXappProgressVisible,
            xaCardinal,
            32,
            0,
            visibleData,
            1);
    Xlib_h.XFlush(x11Session);

    // 更新进度
    for (int i = 0; i <= 100; i+=20) {
        Thread.sleep(500);
        MemorySegment progressData = arena.allocateFrom(ValueLayout.JAVA_INT, i);
        Xlib_h.XChangeProperty(x11Session,
                MemorySegment.ofAddress(rawHandle),
                netWmXappProgress,
                xaCardinal,
                32,
                0,
                progressData,
                1);
        Xlib_h.XFlush(x11Session);
    }

    // 隐藏进度条
    MemorySegment hiddenData = arena.allocateFrom(ValueLayout.JAVA_INT, 0);
    Xlib_h.XChangeProperty(x11Session,
            MemorySegment.ofAddress(rawHandle),
            netWmXappProgressVisible,
            xaCardinal,
            32,
            0,
            hiddenData,
            1);
    Xlib_h.XFlush(x11Session);

    Xlib_h.XCloseDisplay(x11Session);
} catch (Throwable ex) {
    ex.printStackTrace();
}

内容的提问来源于stack exchange,提问作者Petr Štechmüller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:50:04