本文記錄我閱讀 Avalonia 代碼過程中所學習到的在 X11 的窗口之間發送鼠標消息,可以跨進程給其他進程的窗口發送鼠標消息,通過此方式可以實現模擬鼠標輸入
直接使用 XSendEvent 給指定窗口發送消息即可,如以下示例代碼
var xEvent = new XEvent
{
MotionEvent =
{
type = XEventName.MotionNotify,
send_event = true,
window = Window,
display = Display,
x = x,
y = y
}
};
XSendEvent(Display, Window, propagate: false, new IntPtr((int) (EventMask.ButtonMotionMask)), ref xEvent);
以上的 Window 是自己進程的主窗口,發送的相關定義代碼是我從 Avalonia 和 CPF 代碼倉庫裏面抄的,所有代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼
先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 7636387e97780403ce473f553540a9cc1e0652ef
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 7636387e97780403ce473f553540a9cc1e0652ef
獲取代碼之後,進入 DikalehebeekaJaqunicobo 文件夾,即可獲取到源代碼
以上代碼是對自己進程內的主窗口發送鼠標移動消息的示例,核心代碼如下
while (true)
{
var xNextEvent = XNextEvent(Display, out var @event);
if (@event.type == XEventName.MotionNotify)
{
var x = @event.MotionEvent.x;
var y = @event.MotionEvent.y;
XDrawLine(Display, Window, GC, x, y, x + 100, y);
}
var count = XEventsQueued(Display, 0 /*QueuedAlready*/);
if (count == 0)
{
for (int i = 0; i < 100; i++)
{
var xEvent = new XEvent
{
MotionEvent =
{
type = XEventName.MotionNotify,
send_event = true,
window = Window,
display = Display,
x = i,
y = i
}
};
XSendEvent(Display, Window, propagate: false, new IntPtr((int) (EventMask.ButtonMotionMask)), ref xEvent);
}
}
}
如上述代碼可以看到只需更改 XSendEvent 裏面的 Window 對應的參數,即可決定發送給哪個窗口。比如有兩個窗口,可以通過此方式讓窗口 2 收到鼠標消息時,自動轉發給窗口 1 上,核心代碼如下
var handle = XCreateWindow(display, rootWindow, 0, 0, xDisplayWidth, xDisplayHeight, 5,
32,
(int)CreateWindowArgs.InputOutput,
visual,
(nuint)valueMask, ref xSetWindowAttributes);
var window1 = new FooWindow(handle, display);
var window2 = new FooWindow(XCreateWindow(display, rootWindow, 0, 0, xDisplayWidth, xDisplayHeight, 5,
32,
(int) CreateWindowArgs.InputOutput,
visual,
(nuint) valueMask, ref xSetWindowAttributes), display);
while (true)
{
var xNextEvent = XNextEvent(display, out var @event);
if (xNextEvent != 0)
{
break;
}
if (@event.type == XEventName.MotionNotify)
{
var x = @event.MotionEvent.x;
var y = @event.MotionEvent.y;
if (@event.MotionEvent.window == window1.Window)
{
XDrawLine(display, window1.Window, window1.GC, x, y, x + 100, y);
}
else
{
var xEvent = new XEvent
{
MotionEvent =
{
type = XEventName.MotionNotify,
send_event = true,
window = window1.Window,
display = display,
x = x,
y = y
}
};
XSendEvent(display, window1.Window, propagate: false, new IntPtr((int)(EventMask.ButtonMotionMask)),
ref xEvent);
}
}
}
class FooWindow
{
public FooWindow(nint windowHandle, nint display)
{
Window = windowHandle;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask | XEventMask.ResizeRedirectMask |
XEventMask.PointerMotionHintMask;
var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
XSelectInput(display, windowHandle, mask);
XMapWindow(display, windowHandle);
XFlush(display);
var screen = XDefaultScreen(display);
var white = XWhitePixel(display, screen);
var gc = XCreateGC(display, windowHandle, 0, 0);
XSetForeground(display, gc, white);
GC = gc;
}
public nint Window { get; }
public IntPtr GC { get; }
}
以上代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼
先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin c8354f643998d01eed8f56757a558623e4d94a8a
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin c8354f643998d01eed8f56757a558623e4d94a8a
獲取代碼之後,進入 DikalehebeekaJaqunicobo 文件夾,即可獲取到源代碼
以上測試的是相同進程內的,跨進程其實也可以,只需要獲取其他進程的窗口對應的指針即可。其實在這裏我不確定 X11 的窗口 IntPtr 是否稱爲指針是合適的。但行爲上看起來和 Windows 下的句柄非常類似
如以下的測試代碼,啓動自身作爲新的進程,然後傳入當前進程的窗口,讓另一個進程獲取當前進程的窗口,接着測試在另一個進程將鼠標消息發送到當前進程上
var handle = XCreateWindow(display, rootWindow, 0, 0, xDisplayWidth, xDisplayHeight, 5,
32,
(int)CreateWindowArgs.InputOutput,
visual,
(nuint)valueMask, ref xSetWindowAttributes);
var window1 = new FooWindow(handle, display);
XSync(display, false);
IntPtr window2Handle = IntPtr.Zero;
if (args.Length == 0)
{
var currentProcess = Process.GetCurrentProcess();
var mainModuleFileName = currentProcess.MainModule!.FileName;
Process.Start(mainModuleFileName, [window1.Window.ToString(), window1.GC.ToString()]);
}
else if (args.Length == 2)
{
if (long.TryParse(args[0], out var otherProcessWindowHandle))
{
window2Handle = new IntPtr(otherProcessWindowHandle);
}
}
while (true)
{
var xNextEvent = XNextEvent(display, out var @event);
if (xNextEvent != 0)
{
Console.WriteLine($"xNextEvent {xNextEvent}");
break;
}
if (@event.type == XEventName.Expose)
{
if (args.Length == 0)
{
XDrawLine(display, window1.Window, window1.GC, 0, 0, 100, 100);
}
}
else if (@event.type == XEventName.MotionNotify)
{
var x = @event.MotionEvent.x;
var y = @event.MotionEvent.y;
if (window2Handle != 0 && window2GCHandle != 0)
{
// 繪製是無效的
//XDrawLine(display, window2Handle, window2GCHandle, x, y, x + 100, y);
var xEvent = new XEvent
{
MotionEvent =
{
type = XEventName.MotionNotify,
send_event = true,
window = window2Handle,
display = display,
x = x,
y = y
}
};
XSendEvent(display, window2Handle, propagate: false, new IntPtr((int)(EventMask.ButtonMotionMask)),
ref xEvent);
}
else
{
XDrawLine(display, window1.Window, window1.GC, x, y, x + 100, y);
}
}
}
放在 github 和 gitee 上,可以使用如下命令行拉取代碼
先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin ec8242cfe08a0eb23ba637c655083fceb0a8edb3
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin ec8242cfe08a0eb23ba637c655083fceb0a8edb3
獲取代碼之後,進入 DikalehebeekaJaqunicobo 文件夾,即可獲取到源代碼
通過以上測試可以發現 X11 的鼠標輸入是完全可以進行模擬輸入的,只需要拿到窗口指針,使用 XSendEvent 進行發送即可
再進一步的實驗,也許大家也發現上面代碼裏面有被我註釋的 XDrawLine 的調用。在 XDrawLine 裏面也是傳入 GC 和 Window 指針即可繪製線段,我就想着如果傳入別的進程的窗口是否適合,是否就能在其他進程的窗口上繪製出內容
我嘗試從另一個進程將 GC 傳回來,如下面代碼
IntPtr window2Handle = IntPtr.Zero;
IntPtr window2GCHandle = IntPtr.Zero;
if (args.Length == 0)
{
var currentProcess = Process.GetCurrentProcess();
var mainModuleFileName = currentProcess.MainModule!.FileName;
Process.Start(mainModuleFileName, [window1.Window.ToString(), window1.GC.ToString()]);
}
else if (args.Length == 2)
{
if (long.TryParse(args[0], out var otherProcessWindowHandle))
{
window2Handle = new IntPtr(otherProcessWindowHandle);
}
if (long.TryParse(args[1], out var otherProcessGCHandle))
{
window2GCHandle = new IntPtr(otherProcessGCHandle);
}
}
... // 忽略其他代碼
XDrawLine(display, window2Handle, window2GCHandle, x, y, x + 100, y);
此時發現運行代碼,進入到 XDrawLine 報段錯誤,進程掛掉。原因是 gc 指針看起來是不能跨進程使用的,以上代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼
先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin c397b872a4d2cba187e1c04f7b015c8b2ca7092c
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin c397b872a4d2cba187e1c04f7b015c8b2ca7092c
獲取代碼之後,進入 DikalehebeekaJaqunicobo 文件夾,即可獲取到源代碼
嘗試自己進程創建 GC 指針,如以下核心代碼
IntPtr window2Handle = IntPtr.Zero;
IntPtr window2GCHandle = IntPtr.Zero;
if (args.Length == 0)
{
var currentProcess = Process.GetCurrentProcess();
var mainModuleFileName = currentProcess.MainModule!.FileName;
Process.Start(mainModuleFileName, [window1.Window.ToString(), window1.GC.ToString()]);
}
else if (args.Length == 2)
{
if (long.TryParse(args[0], out var otherProcessWindowHandle))
{
window2Handle = new IntPtr(otherProcessWindowHandle);
}
//if (long.TryParse(args[1], out var otherProcessGCHandle))
//{
// window2GCHandle = new IntPtr(otherProcessGCHandle);
//}
// 不用別人傳的,從窗口進行創建
window2GCHandle = XCreateGC(display, window2Handle, 0, 0);
Console.WriteLine($"XCreateGC Window2 {window2GCHandle}");
}
如此代碼經過實際測試發現沒有任何效果,當然了,也不會導致當前進程掛掉。以上代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼
先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin f0cb9bd3b4e4e9184fed831bdd84ef7e4b103888
以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換爲 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin f0cb9bd3b4e4e9184fed831bdd84ef7e4b103888
獲取代碼之後,進入 DikalehebeekaJaqunicobo 文件夾,即可獲取到源代碼
更多 X11 開發請參閱 博客導航