XLib:使用正确翻译的弹出窗口重新显示Java窗口

时间:2015-07-27 06:47:34

标签: 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服务器相对于彼此绘制的,但是所有弹出窗口(以及由Java直接创建的所有窗口,即设置了 override_redirect 标志)将是在错误的位置绘制。

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

然而,此事件必须具有两个特征:   - 不是合成的(即通过 XSendEvent() 调用明确发送)   - 拥有与 ReparentNotify 事件不同的序列号。这是一个棘手的问题,我不知道如何做到这一点,除了在 XReparentWindow 之间插入 sleep(1) XSendEvent XFlush() XSync() XNoOp() < / strong>等等。不起作用,因为他们不创建新请求,因此不会增加序列。

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

1 个答案:

答案 0 :(得分:0)

所以我终于设法让它发挥作用。

以下是需要完成的一系列操作:

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