xeyes的重新实现--x系统及其Xlib以及和windows的异同

本文深入探讨了Xlib编程原理,详细介绍了X系统架构中的客户端-服务器模型,通过具体实例讲解了如何利用Xlib库实现一个简易的xeyes程序,重点阐述了事件循环机制及其优势。

每一个X程序都对应一个x client,x server则只负责输入输出事件和请求,键盘和鼠标以及触摸屏之类的动作会被x server捕获,作为事件通过x协议传给x client,然后x client得到这些事件之后会根据它们做一些逻辑计算,然后得到一个请求通过x协议发给x server,此时x server就会根据x client的请求将图形绘制出来,通过输出设备显示。一个x server可以同时被很多个x client连接,因此它必须管理总体的图形布局,包括哪个窗口属于哪个x client,哪个窗口此时位于最上方,哪个窗口被遮住了哪个部分等等,这些同样作为事件--类似键盘鼠标等的输入事件传给相应的x client,因此可以想象,每一个可以显示的元素都要帮定一个或者几个x client,x client得到这些事件(管理总体布局的事件,而非输入设备的事件)之后,同样要做出一些反应,比如当它的窗口重新显示的时候要重画整个它的窗口等等。
x系统是一个c/s结构的系统,它并不以窗口为核心,而是以进程(x client--xterm/xeyes,x server--xorg...)为核心的,x server进程负责捕获事件--包括输入设备的事件和窗口管理事件,并且将事件传递给相应的x client,而x client则根据事件负责逻辑处理,并将处理结果作为显示请求回传给x server,x server得到请求后进行显示。一般的,每一个x server都有一个事件队列和一个请求队列,每一个事件元素绑定x client,它的大体结构应该包含下面的逻辑:
1.
while(...) {
1.从事件队列取出一个事件;
2.得到事件队应的x client;
3.将事件发给x client;
}
2.
while(...) {
1.从请求队列取出一个请求;
3.根据请求进行显示;
}
3.
3.0:根据鼠标的位置或者(以及)当前窗口得到一个或者多个对之感兴趣的x client用于下面3.1到3.x的事件构造;
3.1:鼠标回调:构造一个事件加入事件队列;
3.2:键盘回调:构造一个事件加入事件队列;
...
x client则同样拥有一个事件队列和一个请求队列,但是逻辑和x server正好相反。
作为一个例子,首先启动一个x server(将配置中的tcpip打开,否则别的机器没法连接),然后用xhost增加一个可以连接的主机,然后在该主机上设置DISPLAY环境变量,最后运行一个最简单的xeyes,然后到x server上移动一下鼠标,在x client上strace一下这个xeyes,希望得到的是当晃动鼠标的时候,strace会打印出tcpip连接的数据,可是结果却很乱,不停的select,不停的超时,不停的打印...看了xeyes的源码才知道,原来它使用的是timer,也就是说不管有没有事件,timer都会定期的到期,然后去select网络连接,也就是和x server的连接。这样岂不是很浪费资源,于是就想用最底层的xlib重写一下著名的xeyes,xlib可以说是比较底层的x编程库了,再往下就是直接用socket用x协议写代码了。顺便说一句,在xeyes的man手册中,有这么一句话:Xeyes watches what you do and reports to the Boss.然而它并没有向任何人报告你的一举一动,看了xeyes的代码后,突然觉得这是可以的,因为XQueryPointer函数可以返回足够的信息,它足以让你的Boss将你fire了,具体的方式就是在XQueryPointer之后以XQueryPointer的第四个参数child_return为参数调用XFetchName(我们简单一点说,实际上还有更多信息可以获取的),然后就可以得到鼠标当前在哪个窗口上,这样Boss就知道你在干什么了,这个信息很容易通过syslog发送出去的,不过前提是Boss的xeyes窗口必须隐藏,也就是必须隐藏那双眼睛。在下面的代码中,我将不处理报告Boss的逻辑,因为上面已经简单说过了。
下面就是事件循环版本的xeyes的源代码了,它除了xlib没有使用其余的库,也没有使用轮询式的timer,而是使用了事件中断的触发机制,只有在有鼠标移动事件的时候xeyes才会行动,再者,所有和数学相关的代码完全拷贝timer版的xeyes代码,下面就是源代码:
#include <X11/Xlib.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/extensions/shape.h>
#include <stdio.h>
#include <math.h>
#include "transform.h"
int main(void)
{
Display *disp = XOpenDisplay(NULL); //得到默认的display
Window win_root = DefaultRootWindow(disp); //如果不使用这个root,将得不到xeyes窗口之外的事件。
int screen = DefaultScreen(display);
int black = BlackPixel(display, screen);
int white = WhitePixel(display, screen);
Window win_eyes = XCreateSimpleWindow(disp, win_root, 0, 0, 150, 100, 3, black, white);
XSelectInput(disp, win_root, PointerMotionMask );
XSelectInput(disp, win_eyes, ExposureMask|PointerMotionMask );
XMapWindow(disp, win_eyes);
XGCValues xgcv1;
xgcv1.function = GXorReverse;
XtGCMask valuemask1 = GCFunction|GCForeground | GCBackground;
GC gc_eyeballs = XCreateGC(disp, win_root, 0, NULL);
XGCValues values_return;
XGetGCValues (disp, gc_eyeballs, valuemask1, &values_return);
Pixel fg = values_return.foreground;
Pixel bg = values_return.background;
xgcv1.foreground = bg;
xgcv1.background = fg; //注意这里创建了两个反转的画笔,作用在于画眼珠子之前先擦除原来的眼珠子
GC gc_balls = XCreateGC(disp, win_root, valuemask1, &xgcv1);
XGCValues xgcv;
Pixmap pim = XCreatePixmap (disp, win_eyes, 150, 100, 1);
XtGCMask valuemask = GCForeground | GCBackground;
GC gc_eyes = XCreateGC (disp, pim, valuemask, &xgcv);
XSetForeground (disp, gc_eyes, 0);
XFillRectangle (disp, pim, gc_eyes, 0, 0,150, 100);
XSetForeground (disp, gc_eyes, 1);
Window rep_root, rep_child;
int rep_rootx, rep_rooty;
unsigned int rep_mask;
int dx, dy;
TPoint mouse;
TPoint newpupil[2]; //记录当次的眼睛位置
TPoint oldpupil[2]; //记录上次的眼睛位置
Transform t; //涉及到数学的计算,全部拷贝原始的xeyes代码
SetTransform (&t,0, 150,100, 0, W_MIN_X, W_MAX_X, W_MIN_Y, W_MAX_Y);
while(1) {
XEvent report = {0};
XNextEvent(disp, &report);
switch (report.type){
case Expose:
if (report.xexpose.window == win_root) {
//一般不会执行到这里,因为我们取的是根窗口,除非创建一个全屏幕的窗口使之遮住根窗口,然后移动它。
} else { //以下的T打头的函数全部拷贝自原始代码
TFillArc (disp, pim, gc_eyes, &t,
EYE_X(0) - EYE_HWIDTH - EYE_THICK,
EYE_Y(0) - EYE_HHEIGHT - EYE_THICK,
EYE_WIDTH + EYE_THICK * 2.0,
EYE_HEIGHT + EYE_THICK * 2.0,
90 * 64, 360 * 64); //画左眼
TFillArc (disp, pim, gc_eyes, &t,
EYE_X(1) - EYE_HWIDTH - EYE_THICK,
EYE_Y(1) - EYE_HHEIGHT - EYE_THICK,
EYE_WIDTH + EYE_THICK * 2.0,
EYE_HEIGHT + EYE_THICK * 2.0,
90 * 64, 360 * 64); //画右眼
//裁剪窗口
XShapeCombineMask (disp, win_eyes, ShapeBounding, 0, 0, pim, ShapeSet);
TFillArc (disp, win_eyes, gc_eyeballs, &t,
EYE_X(0) - EYE_HWIDTH - EYE_THICK,
EYE_Y(0) - EYE_HHEIGHT - EYE_THICK,
EYE_WIDTH + EYE_THICK * 2.0,
EYE_HEIGHT + EYE_THICK * 2.0,
90*64, 360 * 64); //涂黑左眼
TFillArc (disp, win_eyes, gc_balls, &t,
EYE_X(0) - EYE_HWIDTH,
EYE_Y(0) - EYE_HHEIGHT,
EYE_WIDTH, EYE_HEIGHT,
90 * 64, 360 * 64); //描左眼圈
TFillArc (disp, win_eyes, gc_eyeballs, &t,
EYE_X(1) - EYE_HWIDTH - EYE_THICK,
EYE_Y(1) - EYE_HHEIGHT - EYE_THICK,
EYE_WIDTH + EYE_THICK * 2.0,
EYE_HEIGHT + EYE_THICK * 2.0,
90*64, 360 * 64); //涂黑右眼
TFillArc (disp, win_eyes, gc_balls, &t,
EYE_X(1) - EYE_HWIDTH,
EYE_Y(1) - EYE_HHEIGHT,
EYE_WIDTH, EYE_HEIGHT,
90*64, 360 * 64); //描右眼圈
} //不要break,继续画眼珠子
case MotionNotify:
//得到当前鼠标的位置,还顺便得到了它所悬停的窗口信息
XQueryPointer (disp, win_root, &rep_root, &rep_child, &rep_rootx, &rep_rooty, &dx, &dy, &rep_mask);
mouse.x = Tx(dx, dy, &t);
mouse.y = Ty(dx, dy, &t);
computePupils (mouse, newpupil); //数学计算
TFillArc (disp, win_eyes, gc_balls, &t,
oldpupil[0].x - BALL_WIDTH / 2.0,
oldpupil[0].y - BALL_HEIGHT / 2.0,
BALL_WIDTH, BALL_HEIGHT,
90 * 64, 360 * 64); //先用反色清除掉原来的左眼珠子
TFillArc (disp, win_eyes, gc_eyeballs, &t,
newpupil[0].x - BALL_WIDTH / 2.0,
newpupil[0].y - BALL_HEIGHT / 2.0,
BALL_WIDTH, BALL_HEIGHT,
90 * 64, 360 * 64); //再用前景色填充左眼珠子
oldpupil[0].x = newpupil[0].x;
oldpupil[0].y = newpupil[0].y;
TFillArc (disp, win_eyes, gc_balls, &t,
oldpupil[1].x - BALL_WIDTH / 2.0,
oldpupil[1].y - BALL_HEIGHT / 2.0,
BALL_WIDTH, BALL_HEIGHT,
90 * 64, 360 * 64); //右眼珠子同左眼珠子
TFillArc (disp, win_eyes, gc_eyeballs, &t,
newpupil[1].x - BALL_WIDTH / 2.0,
newpupil[1].y - BALL_HEIGHT / 2.0,
BALL_WIDTH, BALL_HEIGHT,
90 * 64, 360 * 64);
oldpupil[1].x = newpupil[1].x;
oldpupil[1].y = newpupil[1].y;
break;
}
}
return 0;
}
Xlib编程很简单,只要几个要素齐备了,那么剩下的就是闭着眼睛敲代码了,首先是一个display--这是一个环境,然后是一个drawable--这是一张画布,它可以是一个windows,最后是一个GC--它是一支画笔,总的情况就是在display中用gc在drawable上作画,一个main函数框架就是:
main()
{
1.得到一个display,作为disp;
2.得到一个drawable,作为一个win;
3.得到一支画笔,作为gc;
4.注册自己感兴趣的事件告诉x server;
5.编写一个事件循环:
while (1) {
5.1.得到一个x server发来的事件;
5.2.处理该事件;
}
}
linux版本的无timer的xeyes到此结束,那么windows的呢?
可以想象,windows的实现更加简单一些,当用vc创建一个win32工程之后,IDE就已经帮你完成一半工作了,仔细理解这一半的工作,发现它和上述xlib编程的main框架是如此的相似。相似之后还有些许不同,这些不同完全是windows的消息机制和x系统的架构机制不同所导致的,x系统的当然要更简单一些,x系统基于进程,而windows基于线程,因此windows的消息机制之所以复杂,那是因为它使用一些操作系统本身的机制,比如线程。如果我们将windows的xeyes应用有意简化,使之向xlib的代码靠拢,最后我们发现,它们更加一致。

<think>好的,用户现在问的是sshssh-X的区别。之前他问过ssh-x怎么用,但可能当时他混淆了大小写或者参数。现在的问题看起来是要明确ssh基本命令ssh -X选项之间的区别。首先,我需要确认用户是否真的了解SSH的基本用法,以及是否有特殊需求。 首先,用户可能在使用SSH连接远程服务器时遇到了图形界面应用无法显示的问题,所以才会提到ssh -X。这时候需要解释清楚,SSH本身是命令行工具,而-X参数用于启用X11转发,允许运行图形程序。需要区分大小写,因为之前用户提到ssh-x,但正确的参数是-X或-Y。 接下来,我需要回顾之前的对话历史。之前已经解释了ssh -X-Y的区别,用户现在可能更深入地问两者的不同。但这次的问题是sshssh-X的区别,也就是基本命令带选项的区别。需要明确说明ssh是基础命令,而-X是启用X11转发的选项。 然后,考虑用户可能的真实需求。用户可能需要在远程服务器上运行图形程序,比如开发工具、编辑器或者可视化工具。需要强调使用场景,比如在Linux服务器上运行gedit或matplotlib绘图时,必须使用-X或-Y参数才能显示图形界面。 同时,用户可能对安全性有疑问,尤其是-Y选项的安全隐患。需要提醒用户,在可信网络中使用-Y,否则优先使用-X。此外,用户可能在Windows环境下使用,需要安装X Server,而Linux/macOS默认支持,这点也需要提到。 还要注意用户可能的误区,比如参数的大小写问题,或者是否真的存在ssh-X这样的命令。需要澄清正确的语法是ssh -X,中间有空格,而不是连在一起的ssh-X。另外,检查用户是否混淆了其他工具,比如是否存在第三方工具叫ssh-x,但通常情况应该是ssh命令的选项。 最后,确保回答结构清晰,分点说明区别,包括用途、命令格式、依赖环境、安全性等。可能还需要给出使用示例,帮助用户理解如何实际操作。同时,提醒常见问题验证方法,比如用xeyes测试是否成功,以及如何排查连接问题。</think>以下是SSH与SSH -X的核心区别详解: --- ### **本质区别** 1. **基础SSH**: - **命令格式**:`ssh username@host` - **功能**:仅建立加密的终端命令行连接 - **使用场景**:纯命令行操作(如编辑文本、执行脚本、管理服务) 2. **SSH -X**: - **命令格式**:`ssh -X username@host` - **功能**:在SSH连接基础上**启用X11图形转发** - **使用场景**:需要运行远程图形界面程序(如GUI编辑器、科学可视化工具) --- ### **技术细节对比** | 特性 | SSH | SSH -X | |---------------------|---------------------|---------------------| | 数据传输类型 | 纯文本/加密命令 | 命令+图形界面数据 | | 依赖环境 | 无需额外配置 | 本地需运行X Server | | 网络延迟敏感度 | 低 | 高(图形传输耗带宽)| | 典型应用 | `vim`, `htop` | `gedit`, `firefox` | --- ### **关键工作机制** 1. **SSH -X实现原理**: - 通过X11协议隧道传输图形数据 - 自动设置`$DISPLAY`环境变量(远程显示到本地) - 映射远程X Window到本地X Server 2. **必要依赖**: ```bash # 远程服务器必须安装的包 sudo apt install xauth ``` 配置文件`/etc/ssh/sshd_config`需包含: ```bash X11Forwarding yes ``` --- ### **安全增强模式** 当`-X`参数出现兼容性问题时,可使用更宽松的`-Y`参数: ```bash ssh -Y user@host # 信任模式转发(降低X11安全限制) ``` **警告**:仅在可信网络使用`-Y`,否则可能暴露本地X Server攻击面 --- ### **操作验证** 连接成功后执行测试: ```bash # 运行测试程序(成功会显示图形窗口) xclock & # 时钟 glxgears & # 3D齿轮(测试OpenGL) ``` --- ### **典型问题解决** 1. **黑屏/无响应**: - 检查本地防火墙是否放行X11端口(默认TCP:6000-6010) - 更新SSH版本:`sudo apt upgrade openssh-client` 2. **报错`Cannot open display`**: ```bash # 手动指定显示设备(通常自动设置无需干预) export DISPLAY=localhost:10.0 ``` --- ### **总结选择建议** - 需要运行**图形程序** ➔ `ssh -X` - 仅需**命令行操作** ➔ 基础SSH - 企业生产环境 ➔ 优先使用基础SSH(降低攻击面)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值