Search code examples
javajavafxx11jna

How to make transparent JavaFX stage transparent for mouse events for Linux X11?


I have a transparent stage and I need when user click inside this stage then mouse event to be passed to a window that is behind my JavaFX stage (for example, if there is a text editor behind my stage then mouse event should be passed to that editor window). By other words I need the Stage to be transparent for mouse events.

This is my code:

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

Could anyone say how to do it, if it is possible.

UPDATE
This is a C program that makes the right half of the window transparent for mouse events (use 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;
}

This is my JavaFX application that using JNA must make the right half of the window transparent for mouse events (by other words I ported makeHalfTransparent function from C to Java):

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

But it doesn't work in JavaFX. Could anyone help me to make JavaFX window transparent for mouse events for Linux X11?


Solution

  • You can try XShapeCombineMask with JNA to make the javafx window transparent for mouse events. Right now, your makeHalfTransparent function looks fine, but you're likely not applying the mask correctly to the javafx window. getWindowPointer needs fetched and applied the shape mask XSHapeCombineMask matching dimensions of the stage and call XFlush after applying the mask