无法获取窗口句柄?

3

我已经搜索了很多地方,尝试找到解决我的困境的答案,但似乎找不到有效的答案。我正在尝试使用Xlib编写一些等效的user32.dll代码,以便支持Linux用户。当然,我正在运行Linux,所以我正在使用Mono。问题出现在这里,因为我甚至无法从Process类中获取窗口句柄,因为它从未被实现:

[MonoTODO]
[DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
[MonitoringDescription ("The handle of the main window of the process.")]
public IntPtr MainWindowHandle {
    get {
        return((IntPtr)0);
    }
}

(源代码)

这真是令人沮丧,因为似乎没有其他办法。我正在尝试这样获取窗口句柄:

[DllImport("libX11")]
private static extern IntPtr XOpenDisplay(IntPtr display);

[DllImport("libX11")]
private static extern int XRaiseWindow(IntPtr display, IntPtr window);

private IntPtr ApplicationHandle;
private IntPtr Display;

private void TestXlib() {
    Process process = Process.GetProcessById(myPid);

    ApplicationHandle = process.MainWindowHandle;

    Display = XOpenDisplay(IntPtr.Zero);

    XRaiseWindow(Display, ApplicationHandle);
}

注意: "myPid"是一个进程ID。请用有效的进程ID替换"myPid"。是的,我确保替换后的"myPid"是有效的进程ID,并且我的代码没有抛出任何指示我使用的进程ID无效的错误。
这不会导致我的应用程序崩溃,但几乎每次调用XRaiseWindow时,它都会打印:
X11 Error encountered: 
  Error: BadWindow (invalid Window parameter)
  Request:     12 (0)
  Resource ID: 0x0
  Serial:      121
  Hwnd:        <null>
  Control:     <null>

因为Process.MainWindowHandle返回的是IntPtr.Zero,所以出现了这个明显的问题。是否有其他方法可以获得窗口句柄?谢谢!

process.MainWindowHandle 对于 Xlib 来说没有意义,从进程 ID 开始,您需要遍历窗口树以找到要提升的窗口。您可以查看 xwininfoC 源代码来了解如何实现... - SushiHangover
@SushiHangover 嗯,坦白地说,我不是C或C++程序员。我知道这对Xlib没有意义,但我想我必须找到一个C++的解决方案。我使用了xwininfo命令来获取窗口句柄。当执行ApplicationHandle = (IntPtr)0x000000;时,它可以正常工作。但这是一种硬编码的解决方案。我在网上看到了一些枚举窗口的代码,但同样,我不是C或C++程序员。 - video_error
你可以通过C#进程使用xwininfo并解析stdout以获取所需的窗口ID,我已经多次这样做来获取整个窗口树或仅一个(通过名称选项)...虽然不如API“干净”,但它“可行”;-) - SushiHangover
@SushiHangover您好,非常感谢!我已经成功完成了,我会发布一些代码给下一个遇到这个问题的人。 - video_error
1
@video_error 你找到解决方案了吗?我也在尝试解决同样的问题。 - tayoung
显示剩余2条评论
1个回答

1
是的,我知道我之前问过这个问题已经很久了,但我现在回答它是因为我在找到解决方案后一直忘记回答它。我最初使用了@SushiHangover的解决方案,但并不是很喜欢它,因为我觉得依赖外部程序(xwininfo)是一个临时的解决方法,并最终增加了另一个依赖项。希望这能帮助其他使用Mono的C#开发人员。这段代码最初是为.NET Framework 2.0编写的。它不太花哨,文档也不是很好。我的解决方案只是通过自己本地枚举窗口并返回所有标题与描述相匹配的窗口。

在X11Wrapper.cs中:

using System;
using System.Runtime.InteropServices;

namespace Program.PInvoke.Xlib {

    public static class X11Wrapper {

        public const string SOName = "libX11.so";

        [DllImport(SOName)]
        // See: https://tronche.com/gui/x/xlib/display/display-macros.html#DefaultRootWindow
        public static extern IntPtr XDefaultRootWindow(IntPtr display);

        [DllImport(SOName)]
        // See: https://tronche.com/gui/x/xlib/window-information/XQueryTree.html
        public static extern int XQueryTree(IntPtr display, IntPtr w,
                                            out IntPtr root_return, out IntPtr parent_return,
                                            out IntPtr[] children_return, out int nchildren_return);

        [DllImport(SOName)]
        // See: https://tronche.com/gui/x/xlib/ICC/client-to-window-manager/XFetchName.html
        public static extern int XFetchName(IntPtr display, IntPtr w,
                                            out string window_name_return);
    }
}

在Linux.Utilities.cs文件中:
using Program.PInvoke.Xlib;

namespace Program {

    public static partial class Utilities {

        public static bool IsUnix {
            get {
                return Environment.OSVersion.
                       Platform == PlatformID.Unix;
            }
        }

        private static IntPtr[] FindChildWindows(IntPtr display, IntPtr window,
                                                 string title, ref List<IntPtr> windows) {
            IntPtr rootWindow;
            IntPtr parentWindow;

            IntPtr[] childWindows = new IntPtr[0];

            int childWindowsLength;

            X11Wrapper.XQueryTree(display, window,
                                  out rootWindow, out parentWindow,
                                  out childWindows, out childWindowsLength);

            childWindows = new IntPtr[childWindowsLength];

            X11Wrapper.XQueryTree(display, window,
                                  out rootWindow, out parentWindow,
                                  out childWindows, out childWindowsLength);

            string windowFetchedTitle;

            X11Wrapper.XFetchName(display, window, out windowFetchedTitle);

            if(title == windowFetchedTitle &&
               !windows.Contains(window)) {
                windows.Add(window);
            }

            for(int childWindowsIndexer = 0;
                childWindowsIndexer < childWindows.Length;
                childWindowsIndexer++) {
                IntPtr childWindow = childWindows[childWindowsIndexer];

                string childWindowFetchedTitle;

                X11Wrapper.XFetchName(display, childWindow,
                                      out childWindowFetchedTitle);

                if(title == childWindowFetchedTitle &&
                   !windows.Contains(childWindow)) {
                    windows.Add(childWindow);
                }

                FindChildWindows(display, childWindow, title, ref windows);
            }

            windows.TrimExcess();

            return windows.ToArray();
        }

        public static IntPtr[] FindWindows(IntPtr display, string title) {
            List<IntPtr> windows = new List<IntPtr>();

            return FindChildWindows(display,
                                    X11Wrapper.XDefaultRootWindow(display),
                                    title,
                                    ref windows);
        }
    }
}

注:我最初表示我不是C开发人员(事情已经改变,我学会了C),因此我对使用Interop自己实现功能感到犹豫。如果您像我一样更多地使用Xlib,则可以考虑使用tronche作为Xlib API参考。它是用C编写的,但我发现将其翻译为可PInvoke函数和可封送结构在C#中相当容易。还有一些要考虑的好注意事项。另一个帮助翻译的好资源是直接使用源代码查找低级类型的定义,以帮助找到C#等效项。类似这样的东西应该会大大帮助您:http://refspecs.linuxbase.org/LSB_4.0.0/LSB-Desktop-generic/LSB-Desktop-generic/libx11-ddefs.html


1
哥们,非常感谢!这个方法完美地解决了窗口问题!我尝试了各种方法,但都不起作用。你救了我的命,我无法表达我的感激之情! - tayoung
没问题!很高兴能为您提供帮助。祝您在未来的本地 Linux 事业中好运。 - video_error
或者说它曾经能获取信息,但现在却不能了。我现在毫无头绪,不知道发生了什么事。 - tayoung
首先,感谢您的帮助。我现在可以使用您的示例获取窗口句柄了。但出于某种原因,XRaiseWindow 仍然无法正常工作。它不会抛出错误,但也不会提升窗口(我还尝试了 XSetInputFocus,但似乎也不起作用...即不能将键盘“焦点”设置到窗口)。这对您来说是这样吗?还有其他可能遗漏的东西吗? - MikeFromCanmore
@MikeFromCanmore 很抱歉,Mike。我不太清楚为什么会发生这种情况。我最初在我的Chromebook Ubuntu-LXDE chroot上测试/使用了上述代码(Works on my machine™)。我不知道为什么XRaiseWindow不能像预期那样正常工作。一个好的检查位置是 Xorg 配置/扩展,可能会影响到这一点。如果那里没有任何问题,那么它可能仍然没有抓取正确的窗口句柄,因为 XRaiseWindow 只有在存在无效的窗口句柄时才会出错。Mono 捕获了本机异常并警告了我,但它可能不会警告您。 - video_error
没问题,如果我解决了这个问题,我会回报的。我在想问题可能出在我使用的X11包装器上。我从这里获取了我的包装器,并根据你的示例添加了缺失的接口:“https://www.codeproject.com/Articles/584743/Programming-Xlib-with-Mono-Deve”。 - MikeFromCanmore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接