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
相关产品推荐
相关产品推荐

