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

Linux X11下如何实现JavaFX透明Stage的鼠标事件穿透

Linux X11下JavaFX透明窗口实现鼠标事件穿透问题

需求

需要创建一个透明的JavaFX Stage,当用户点击该Stage时,鼠标事件能传递到后方窗口(例如文本编辑器),即实现Stage的鼠标事件穿透效果。

初始尝试的JavaFX代码

public class Test12 extends Application {

    @Override
    public void start(Stage primaryStage) {
        var vBox = new VBox();
        vBox.setStyle("-fx-border-width: 1; -fx-border-color: black");
        vBox.setMouseTransparent(true);
        var scene = new Scene(vBox, 400, 300);
        scene.setFill(Color.TRANSPARENT);
        scene.getStylesheets().add(this.getClass().getResource("test.css").toExternalForm());
        primaryStage.initStyle(StageStyle.TRANSPARENT);
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.setAlwaysOnTop(true);

    }

    public static void main(String[] args) {
        launch(args);
    }
}

配套CSS代码

.root { -fx-background-color: transparent; }

可用的C语言实现(X11平台)

以下C程序可实现窗口右半部分的鼠标事件穿透,编译命令:gcc -o program program.c -lX11 -lXext

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <X11/extensions/shape.h>

/* ----------------------------------------------------------------------------------------------- */
#define _NET_WM_STATE_REMOVE        0    // remove/unset property
#define _NET_WM_STATE_ADD           1    // add/set property
#define _NET_WM_STATE_TOGGLE        2    // toggle property

Bool makeAlwaysOnTop(Display* display,  Window mywin) {
    int screen = DefaultScreen(display);
    Window root = RootWindow(display, screen);
    Atom wmStateAbove = XInternAtom( display, "_NET_WM_STATE_ABOVE", 1 );
    if( wmStateAbove != None ) {
        printf( "_NET_WM_STATE_ABOVE has atom of %ld\n", (long)wmStateAbove );
    } else {
        printf( "ERROR: cannot find atom for _NET_WM_STATE_ABOVE !\n" );
        return False;
    }

    Atom wmNetWmState = XInternAtom( display, "_NET_WM_STATE", 1 );
    if( wmNetWmState != None ) {
        printf( "_NET_WM_STATE has atom of %ld\n", (long)wmNetWmState );
    } else {
        printf( "ERROR: cannot find atom for _NET_WM_STATE !\n" );
        return False;
    }

    if( wmStateAbove != None )
    {
        XClientMessageEvent xclient;
        memset( &xclient, 0, sizeof (xclient) );
        xclient.type = ClientMessage;
        xclient.window = mywin;
        xclient.message_type = wmNetWmState;
        xclient.format = 32;
        xclient.data.l[0] = _NET_WM_STATE_ADD;
        xclient.data.l[1] = wmStateAbove;
        xclient.data.l[2] = 0;
        xclient.data.l[3] = 0;
        xclient.data.l[4] = 0;
        XSendEvent( display,
          root,
          False,
          SubstructureRedirectMask | SubstructureNotifyMask,
          (XEvent *)&xclient );
        XFlush(display);
        return True;
    }
    return False;
}

/* ----------------------------------------------------------------------------------------------- */

void makeHalfTransparent(Display* dpy,  Window win) {
    Pixmap shape = XCreatePixmap(dpy, win, 400, 300, 1);
    GC gc = XCreateGC(dpy, shape, 0, NULL);
    XSetFillStyle(dpy, gc, FillSolid);
    XSetForeground(dpy, gc, 0);
    XFillRectangle(dpy, shape, gc, 0, 0, 400, 300);
    XSetForeground(dpy, gc, 1);
    XFillRectangle(dpy, shape, gc, 0, 0, 200, 300);
    XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet);
    XFreeGC(dpy, gc);
}

/* ----------------------------------------------------------------------------------------------- */

int main() {
    Display *dpy;
    Window win;

    XEvent event;
    int screen;

    dpy = XOpenDisplay(NULL);
    if (dpy == NULL) {
        fprintf(stderr, "Cannot open display\n");
        return 1;
    }

    screen = DefaultScreen(dpy);
    win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), 10, 10, 400, 300, 1, BlackPixel(dpy, screen), WhitePixel(dpy, screen));
    XSelectInput(dpy, win, ExposureMask | KeyPressMask);

    XMapWindow(dpy, win);
    makeAlwaysOnTop(dpy, win);
    makeHalfTransparent(dpy, win);

    while (1) {
        XNextEvent(dpy, &event);
        if (event.type == KeyPress) {
            break;
        }
    }

    XDestroyWindow(dpy, win);
    XCloseDisplay(dpy);
    return 0;
}

移植后的JavaFX+JNA代码

将上述C代码中的makeHalfTransparent函数通过JNA移植到JavaFX应用中,但该方案无效:

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.unix.X11;
import com.sun.jna.ptr.PointerByReference;
import java.lang.reflect.Method;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.StageStyle;


public class JavaFxTest10 extends Application {

    public static interface CustomX11 extends Library {

        CustomX11 INSTANCE = (CustomX11) Native.load("X11", CustomX11.class);

        X11.Display XOpenDisplay(String display_name);

        int XFreeGC(X11.Display display, X11.GC gc);

        void XSetForeground(X11.Display display, X11.GC gc, int foreground);

        X11.Pixmap XCreatePixmap(X11.Display display, X11.Window w, int width, int height, int depth);

        void XFillRectangle(X11.Display display, X11.Pixmap drawable, X11.GC gc, int x, int y, int width, int height);

        X11.GC XCreateGC(X11.Display display, X11.Pixmap drawable, int valuemask, X11.XGCValues values);

        void XFreePixmap(X11.Display display, X11.Pixmap pixmap);

        int XFetchName(X11.Display display, Window w, PointerByReference windowName);

        X11.Window XDefaultRootWindow(X11.Display display);

        X11.Window XCreateSimpleWindow(X11.Display display, X11.Window rootWindow, int a, int b, int c, int d, int e, int h, int g);

        X11.Atom XInternAtom(X11.Display display, String atom_name, boolean only_if_exists);

        void XChangeProperty(X11.Display display, X11.Window window, X11.Atom property, X11.Atom type, int format, int mode, Pointer data, int nelements);

        void XSetFillStyle(X11.Display display, X11.GC gc, int fillStyle);

        void XFillRectangle(X11.Display display, X11.Drawable drawable, X11.GC gc, int x, int y, int width, int height);
    }

    public interface Xext extends Library {

       Xext INSTANCE = Native.load("Xext", Xext.class);

       void XShapeCombineMask(X11.Display display, X11.Window w, int shapeOp, int x, int y, X11.Pixmap shape, int shapeKind);
    }

    @Override
    public void start(Stage primaryStage) {
        var button = new Button("Push Me");
        button.setOnAction(e -> {
            var p = getWIndowPoiter(primaryStage);
            var window = getWindow(p);
            printWindowTitle(window);//checking pointer
            makeHalfTransparent(window, (int) primaryStage.getWidth(), (int) primaryStage.getHeight());
        });
        var hBox = new HBox(button);
        hBox.setStyle("-fx-background-color: green");
        var content = new VBox();
        content.setMouseTransparent(true);
        VBox.setVgrow(content, Priority.ALWAYS);

        var mainBox = new VBox(hBox, content);
        mainBox.setStyle("-fx-border-width: 1; -fx-border-color: black");
        var scene = new Scene(mainBox, 400, 300);
        scene.setFill(Color.TRANSPARENT);
        scene.getStylesheets().add(this.getClass().getResource("test.css").toExternalForm());
        primaryStage.initStyle(StageStyle.TRANSPARENT);
        primaryStage.setScene(scene);
        primaryStage.show();
        primaryStage.setAlwaysOnTop(true);
        primaryStage.setTitle("JafaFX Window 777");
    }

    private Pointer getWIndowPoiter(Stage stage) {
        try {
            final Method getPeer = Window.class.getDeclaredMethod("getPeer", null);
            getPeer.setAccessible(true);
            final Object tkStage = getPeer.invoke(stage);
            final Method getRawHandle = tkStage.getClass().getMethod("getRawHandle");
            getRawHandle.setAccessible(true);
            var l = (Long) getRawHandle.invoke(tkStage);
            final Pointer pointer = new Pointer(l);
            System.out.println(pointer);
            return pointer;
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
            ex.printStackTrace();
            return null;
        }
    }

    private X11.Window getWindow( Pointer windowPointer) {
        X11.Window window = new X11.Window(Pointer.nativeValue(windowPointer));
        return window;
    }

    private void printWindowTitle(X11.Window window) {
        final X11 x11 = X11.INSTANCE;
        X11.Display display = x11.XOpenDisplay(null);
        PointerByReference windowNameReference = new PointerByReference();
        int status = X11.INSTANCE.XFetchName(display, window, windowNameReference);

        if (status == 1) {
            Pointer windowNamePointer = windowNameReference.getValue();
            String windowTitle = windowNamePointer.getString(0);
            System.out.println("Window Title: " + windowTitle);
        } else {
            System.out.println("Failed to fetch window name.");
        }
    }

    public static final int FillSolid = 0;
    public static final int ShapeBounding = 0;
    public static final int ShapeSet = 0;

    private void makeHalfTransparent(X11.Window win, int width, int height) {
        X11.Display display = CustomX11.INSTANCE.XOpenDisplay(null);
        //Pixmap shape = XCreatePixmap(dpy, win, 400, 300, 1);
        X11.Pixmap shape = CustomX11.INSTANCE.XCreatePixmap(display, win, width, height, 1);
        //GC gc = XCreateGC(dpy, shape, 0, NULL);
        X11.GC gc2 = CustomX11.INSTANCE.XCreateGC(display, shape, 0, null);
        //XSetFillStyle(dpy, gc, FillSolid);
        CustomX11.INSTANCE.XSetFillStyle(display, gc2, FillSolid);
        //XSetForeground(dpy, gc, 0);
        CustomX11.INSTANCE.XSetForeground(display, gc2, 0);
        //XFillRectangle(dpy, shape, gc, 0, 0, 400, 300);
        CustomX11.INSTANCE.XFillRectangle(display, shape, gc2, 0, 0, width, height);
        //XSetForeground(dpy, gc, 1);
        CustomX11.INSTANCE.XSetForeground(display, gc2, 1);
        //XFillRectangle(dpy, shape, gc, 0, 0, 200, 300);
        CustomX11.INSTANCE.XFillRectangle(display, shape, gc2, 0, 0, width / 2, height);
        //XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shape, ShapeSet);
        Xext.INSTANCE.XShapeCombineMask(display, win, ShapeBounding, 0, 0, shape, ShapeSet);
        //XFreeGC(dpy, gc);
        CustomX11.INSTANCE.XFreeGC(display, gc2);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

求助

上述JavaFX+JNA的实现方案无效,需要解决Linux X11环境下JavaFX窗口的鼠标事件穿透问题。

内容的提问来源于stack exchange,提问作者SilverCube

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 07:39:50