【问题标题】:XLib: Reparenting a Java window with popups properly translatedXLib:使用正确翻译的弹出窗口重新设置 Java 窗口
【发布时间】:2015-07-27 06:47:33
【问题描述】:

我有一个应用程序可以抓取 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.javaXWM.java 中。

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

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

在没有 WM 的情况下,Java 仍会对其接收到的各种 X 事件(单击、移动、重新设置 ..)做出反应,但预计会有非常严格的事件连续性。

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

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

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

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

【问题讨论】:

  • 为什么不直接运行一个真正的窗口管理器?
  • 不幸的是,这不是一个选择......

标签: java x11 xlib


【解决方案1】:

所以我终于设法让它工作了。

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

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

【讨论】:

    猜你喜欢
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-17
    • 2012-07-08
    • 2013-06-06
    • 1970-01-01
    • 2022-01-03
    相关资源
    最近更新 更多