java - XLib:使用正确翻译的弹出窗口来代替Java窗口

标签 java x11 xlib

我有一个可以捕获X窗口并将其作为子窗口托管的应用程序。
它以某种方式实现了一个非常基本的窗口管理器。没有窗口管理器正在运行。
除Java应用程序外,效果很好。

例如,如果Java应用程序具有菜单或弹出窗口,则这些弹出窗口的位置不正确。
我已经用一个非常简单的单元测试来复制了它。
Java应用程序是带有菜单栏和菜单的基本JFrame

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JMenuBar;

public class test extends JFrame {
    private static test window;
    public test() {
        initialize();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("Hello world");
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                window = new test();
                window.setVisible(true);
            }
        });
    }

     /**
     * Initialize the contents of the frame.
     */
    private void initialize() {          
        setTitle("ResizeTest");

        setBounds(100, 100, 888, 439);
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        getContentPane().setLayout(new BorderLayout(0, 0));

        JMenuBar menuBar = new JMenuBar();
        getContentPane().add(menuBar, BorderLayout.NORTH);

        JMenu fileMenu = new JMenu("File");
        fileMenu.add("Open");
        fileMenu.add("Close");
        menuBar.add(fileMenu);
        add(menuBar);

        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                dispose();
                System.exit(0);
            }
        });

        JPanel panel = new JPanel();
        panel.add(new JLabel("Hello world"));
        getContentPane().add(panel, BorderLayout.SOUTH);

        pack();
    }
}


然后另一个单元测试(在C中):

/*
   Simple Xlib application drawing a box in a window.
   To Compile: gcc -O2 -Wall -o test test.c -L /usr/X11R6/lib -lX11 -lm
 */ 
#include<X11/Xlib.h>
#include<stdio.h>
#include<stdlib.h> // prevents error for exit on line 18 when compiling with gcc

#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>

int WINDOW_ID;

void processExistingWindows(Display *display, Window parentWindow) {

    Window *children;
    Window parent;
    Window root;
    unsigned int nchildren;
    printf("Entering processExistingWindows\n");

    Window rootWindow = XDefaultRootWindow(display);
    int result = XQueryTree(display, rootWindow, &root, &parent, &children, &nchildren);
    printf("XQueryTree result is %d\n", result);
    unsigned int windowCount = 0;
    printf("Iterating through %d windows\n", nchildren);
    for (windowCount = 0; result && windowCount < nchildren; windowCount++) {
        Window currentWindow = children[windowCount];
        if ((int)currentWindow == WINDOW_ID) {
            int reparentResult = XReparentWindow(display, currentWindow, parentWindow, 0, 0);
            printf("XReparentWindow result: %d\n", reparentResult);
            printf("Reparented window: %x into %x\n", (int)currentWindow, (int)parentWindow);
        }
    }
    if (result && children != NULL) {
        XFree((char *) children);
    }
}

 int main(int argc, char** argv) {
   Display *d;
   int s;
   Window w;
   XEvent e;

   if (argc < 2) {
      printf("Please give the window ID you want to reparent\n");
      exit(1);
    }
    WINDOW_ID = atoi(argv[1]);
    printf("Will try to reparent the Window: %x \n", WINDOW_ID);

    printf("Forcing synchronous calls to the X Server\n");
    _Xdebug = 1; // To allow proper debugging by forcing synchronous calls to the X Server

                /* open connection with the server */
   d=XOpenDisplay(NULL);
   if(d==NULL) {
     printf("Cannot open display\n");
     exit(1);
   }
   s=DefaultScreen(d);

    /* create window */
   w = XCreateSimpleWindow(d, RootWindow(d, s), 0, 0, 400, 400, 1,
                         BlackPixel(d, s), WhitePixel(d, s));

    printf("Current window ID is: %x\n", (int) w);

   // Process Window Close Event through event handler so XNextEvent does Not fail
   Atom delWindow = XInternAtom( d, "WM_DELETE_WINDOW", 0 );
   XSetWMProtocols(d , w, &delWindow, 1);

                        /* select kind of events we are interested in */
   XSelectInput(d, w, ExposureMask | KeyPressMask | StructureNotifyMask );

                        /* map (show) the window */
   XMapWindow(d, w);

   processExistingWindows(d, w);
                        /* event loop */
   printf("Starting the loop\n");
   XMoveWindow(d, w, 100, 200);

   while(1) {
     XNextEvent(d, &e);
                        /* draw or redraw the window */
     if(e.type==Expose) {
       XFillRectangle(d, w, DefaultGC(d, s), 20, 20, 10, 10);
     }
                        /* exit on key press */
     if(e.type==KeyPress)
       break;

     // Handle Windows Close Event
     if(e.type==ClientMessage)
        break;
   }

                        /* destroy our window */
   XDestroyWindow(d, w);

                        /* close connection to server */
   XCloseDisplay(d);

   return 0;
 }


只需启动Java应用程序。
它会正确显示,并且菜单会显示预期的位置。

抓取窗口ID(使用xwininfo -root -tree -int),并以该窗口ID作为参数启动单元测试,以在基本窗口中重新显示它。
Java框架已正确移动并重新放置在窗口中,但是菜单仍在先前的JFrame位置弹出!

就像JFrame相对于其新的父窗口正确地重新定位了其原点,但是菜单/弹出窗口未考虑到此新位置。

这很奇怪。
如果不是执行XReparentWindow而是执行XMoveWindow,则菜单会出现在应该显示的位置。

我用谷歌搜索了这个问题,没有任何东西可以带来任何明确的解决方案。
关于JVM中的切片窗口管理器的硬编码支持,有很多讨论,而OpenJDK显然解决了这一问题。但是我所采用的版本都没有起作用(Java 6、7、8和OpenJDK 7)。

尽管在我看来,它看起来像是JDK中的一个普通错误(我以这种方式托管了其他基于C / C ++的应用程序,可以与菜单/弹出窗口很好地配合使用),但使用窗口管理器(如FVWM)运行的事实证明了它可以工作。
我还查看了twm中的实现,它看起来没有什么不同。

有没有人遇到(并解决)这个问题?

谢谢!



编辑

经过数小时的调试,它的确来自Java及其如何处理窗口管理器(或不存在)。该代码有趣的部分在XDecoratedPeer.java和XWM.java中。

Java会尝试猜测正在运行的窗口管理器,并会基于此窗口管理器调整其行为(并非所有人都以相同的方式对待客户端应用程序)。在我们的例子中,没有窗口管理器在运行,因此Java将默认为No / Other WM。

一旦启动,Java应用程序将永远不会重新检查窗口管理器的更改。因此,在主机内部我们无法“伪造”任何特定的WM来更改来宾的行为,因为为时已晚。

在没有WM的情况下,Java仍然会对收到的各种X事件做出反应(单击,移动,重设……),但是期望事件的序列非常严格。

在我们的例子中,ReparentNotifyEvent不足以触发对JVM中窗口位置的完全重新计算。因此,在从主机重新定向之后,来宾仍然认为它在其以前的位置。这不是立即可见的,因为所有窗口都是由X Server相对于彼此绘制的,但是所有弹出窗口(以及所有Java直接创建的窗口,即设置了override_redirect标志)将在错误的位置绘制。

Java需要重新计算其屏幕坐标的是ConfigureNotifyEvent(包含绝对屏幕坐标)。

但是,此事件必须具有两个特征:
 -不能合成(即通过XSendEvent()调用显式发送)
 -具有与ReparentNotify事件不同的序列。这有点棘手,除了在XReparentWindow和XSendEvent之间插入sleep(1)外,我不知道该怎么做。 XFlush(),XSync(),XNoOp()等无法使用,因为它们没有创建新的请求,因此不会增加序列号。

我研究过的所有Java版本都具有几乎相同的行为。
无论如何,通过此更改,Java客户端可以正确地重新计算其屏幕位置,并且现在可以正确显示弹出窗口。

最佳答案

所以我终于设法使它起作用。

以下是需要执行的操作序列:

// We need to move the child window before reparenting it to avoid some nasty offsets
XMoveWindow(display, childWindowId, 0, 0);

// Do the reparenting
XReparentWindow(display, childWindowId, parentWindowId, 0, 0);

// Ask the XServer to take ownership back if we die
XFixesChangeSaveSet(display, childWindowId, SetModeInsert, SaveSetRoot, SaveSetUnmap);

// We have to explicitly notify the Java child of its location change.
XEvent client_event;
XWindowAttributes childAttributes;
XWindowAttributes parentAttributes;
XGetWindowAttributes(display, childWindowId, &childAttributes);
XGetWindowAttributes(display, parentWindowId, &parentAttributes);
WindowDimension windowDecorationSize = // Your decoration if applicable

client_event.type = ConfigureNotify;
client_event.xconfigure.send_event = True;
client_event.xconfigure.display = display;
client_event.xconfigure.event = childWindowId;
client_event.xconfigure.window = childWindowId ;
client_event.xconfigure.x = parentAttributes.x + windowDecorationSize.width;
client_event.xconfigure.y = parentAttributes.y + windowDecorationSize.height;
client_event.xconfigure.width = childAttributes.width;
client_event.xconfigure.height = childAttributes.height;
client_event.xconfigure.border_width = 0;
client_event.xconfigure.above = None;
client_event.xconfigure.override_redirect = True;   // Set to true to filter the event out in the processing of the parent Java frame 

XSendEvent(display, childWindowId, False, StructureNotifyMask, &client_event);

关于java - XLib:使用正确翻译的弹出窗口来代替Java窗口,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31646544/

相关文章:

java - 同一类的两个类是否具有相同的哈希码,它们是否被认为是相等的?

linux - 直接打开到 Xserver 的套接字

c++ - X11,更改分辨率并使窗口全屏

c++ - Xlib的Colormap在哪里定义的?

c - 使用 Xlib 放大图像

java - 无法从文本文件读取数据

java - 在 ANDroid 中读取和写入图像到 SDCard

java - System.in 中的 BufferedReader 输入尝试抛出异常

c - 为什么没有边框的窗口总是在最上面

x11 - 如何更改 X 窗口属性