【问题标题】:Capturing Display / Monitor Images, Sending Keyboard Input on Linux捕获显示/监视器图像,在 Linux 上发送键盘输入
【发布时间】:2011-08-28 14:34:49
【问题描述】:

我需要处理发送到笔记本电脑视频显示器的图像,并且需要使用 C++ 或 shell 程序将键盘输入发送到我的 Linux 系统。

我的目标是处理属于 FPS 游戏一部分的图像,然后根据这些图像在该游戏中采取行动(因此是键盘输入)。我没有试图理解(如果可能的话)如何使用一些 API 来连接游戏 X 或 Y,我认为这是连接任何游戏的最快方式,以某种方式劫持 Linux 输入和输出。

有没有任何方法可以在没有任何内核或设备驱动程序黑客攻击的情况下做到这一点?我之前使用 recordmydesktop 将我的桌面录制为视频,我想我可以破解它的代码并尝试从中进行逆向工程。还有其他想法吗?我在 Ubuntu 11 上。

Related Question

【问题讨论】:

    标签: linux linux-kernel x11 linux-device-driver function-interposition


    【解决方案1】:

    您不需要做任何像内核或设备驱动程序那样低级的事情来做到这一点。

    您可以使用XTest X11 extension 以编程方式伪造输入事件,例如(来自this posting,有another example for keyboard)。

    #include <X11/extensions/XTest.h>  
    #include <unistd.h>  
    
    int main ()  
    {  
      Display *dpy = NULL;  
      XEvent event;  
    
      dpy = XOpenDisplay (NULL);  
    
      /* Get the current pointer position */  
      XQueryPointer (dpy, RootWindow (dpy, 0),  
            &event.xbutton.root, &event.xbutton.window,  
            &event.xbutton.x_root, &event.xbutton.y_root,  
            &event.xbutton.x, &event.xbutton.y,  
            &event.xbutton.state);  
    
      /* Fake the pointer movement to new relative position */  
      XTestFakeMotionEvent (dpy, 0, event.xbutton.x + 100,  
            event.xbutton.y + 50, CurrentTime);  
      XSync(dpy, 0);  
      XCloseDisplay (dpy);  
      return 0;  
    }   
    

    要捕获图像,最简单的方法是使用function interposition(通过LD_PRELOAD)“拦截”对glXSwapBuffers 的调用,每绘制一帧就会调用它。您可以从那里复制帧缓冲区的内容,使用glReadPixels 并按照您的意愿使用它。

    例如未经测试的轮廓用于拦截 OpenGL 帧:

    // Function pointer to the *real* glXSwapBuffers
    static void (*glx_fptr)(Display*, GLXDrawable) = NULL;
    
    // Make sure init gets called when the shared object is loaded. GCC specific.
    static void init(void)  __attribute__((constructor));
    
    static void init(void) {
        dlerror();
        // find the real glXSwapBuffers
        glx_fptr = dlsym(RTLD_NEXT, "glXSwapBuffers");
        if (NULL == glx_fptr)
            fprintf(stderr, "[glvidcap] %s\n", dlerror());
    }
    
    void glXSwapBuffers(Display *dpy, GLXDrawable drawable) {
        unsigned int w = 0;
        unsigned int h = 0;
        static int x,y;
        static Window win;
        static unsigned int border,depth;
        // Find the window size. (You could skip this and make it all static if you
        // Trust the window not to change size
        XGetGeometry(dpy, drawable, &win, &x, &y, &w, &h, &border, &depth);
    
        // Assuming frame is some memory you want the frame dumped to:
        glReadPixels(0,0,w,h,GL_BGR,GL_UNSIGNED_BYTE, frame);
    
        // Call the real function:
        assert(glx_fptr);
        glx_fptr(dpy, drawable);
    }
    

    然后您想在运行您正在查看的任何游戏之前将其编译为共享对象和LD_PRELOAD 该共享对象。

    如果它恰好是 SDL 应用程序,您可以酌情拦截对 SDL_FlipSDL_UpdateRect 的调用。

    【讨论】:

    • glReadPixels 相对于当前设置的视口。您应该首先读取最后设置的视口尺寸 (glGetIntegerv(GL_VIEWPORT)),将视口大小设置为窗口尺寸,获取旧的读取缓冲区 (glGetIntegerv(GL_READ_BUFFER)),将读取缓冲区设置为 GL_BACK,然后调用 glReadPixels。之后将事情恢复到以前的状态。
    • @awoodland,使用演示 OpenGL 代码,我可以使用 glcapture 捕获和写入图像文件。然而,在 UrbanTerror 上只调用了 gettimeofday,而不调用 glXSwapBuffers。你知道为什么会这样吗?我的代码在这里 - goo.gl/Gxyha
    • @user423805 - 我猜它正在进行一些替代调用,而不仅仅是交换缓冲区。您也许可以通过嗅探 X11 调用的东西来弄清楚它是什么,例如xmsgtrace.sourceforge.netx.org/archive/X11R7.5/doc/man/man1/xscope.1.html 但它们可能不适用于 OpenGL 应用程序。
    【解决方案2】:

    感谢@awoodland的回答,我找了相关资源,找到了这个

    http://bzr.sesse.net/glcapture/glcapture.c

    我将此代码编译为

    gcc -shared -fPIC -o glcapture.so glcapture.c -ldl
    

    并在脚本中为 FPS 游戏 Urban Terror 加载它

    LD_PRELOAD=`pwd`/glcapture.so [DIR]/UrbanTerror/ioUrbanTerror.i386
    

    运行此脚本的那一刻,游戏就被加载了。出于某种原因,游戏图形不显示,但我可以稍后改进。当我退出游戏时,我看到控制台上打印了 gettimeofday 消息,这告诉我钩子起作用了。我将在继续处理此代码时提供更多详细信息。

    为了发送按键,我点击了链接

    http://bharathisubramanian.wordpress.com/2010/03/14/x11-fake-key-event-generation-using-xtest-ext/

    在我用

    在 Ubuntu 上安装了必要的软件包之后
    sudo apt-get install libxtst-dev
    

    然后 fakeKey.c 编译并没有问题地工作。

    注:本人started a project on Github for this,欢迎大家fork、帮助等。

    【讨论】:

      【解决方案3】:

      我终于有办法了。我相信 UrT 会自行加载 OpenGL,因此无法进行墙黑客等操作。那么剩下的最好的选择是截取 X 个屏幕截图。即使使用像 Python 这样的脚本语言,它的工作速度也非常快。以下代码获取连续的屏幕截图并通过 OpenCV 将它们显示为动画。当然,您需要以最小化模式启动 UrT。 The rest of the details are in my project.

      import gtk.gdk
      import PIL
      from opencv.cv import *
      from opencv.highgui import *
      from opencv.adaptors import PIL2Ipl
      
      w = gtk.gdk.get_default_root_window()
      sz = w.get_size()
      print "The size of the window is %d x %d" % sz
      
      size_x = 600
      size_y = 400
      start_x = 0
      start_y = 100
      end_x = start_x+size_x
      end_y = start_y+size_y
      box = (start_x, start_y, start_x+size_x, start_y+size_y)
      
      while True:
          pb = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,False,8,sz[0],sz[1])
          pb = pb.get_from_drawable(w,w.get_colormap(),0,0,0,0,sz[0],sz[1])
          width,height = pb.get_width(),pb.get_height()
          im = PIL.Image.fromstring("RGB",(width,height),pb.get_pixels())
          im = im.crop(box)
          cv_img = PIL2Ipl(im)
          cvNamedWindow("fps")
          cvShowImage("fps", cv_img)
          cvWaitKey(30) 
      

      另外,为了给游戏发送钥匙,上面的方法不起作用,我不得不使用xdotool来发送前进键到UrT,

      xdotool search --name ioUrbanTerror  windowactivate keydown W
      

      【讨论】:

      • 如果它直接使用dlopen() 加载 OpenGL,您仍然可以将 /usr/lib 中的 libGL.so 替换为您自己的 libGL.so,尽管您的解决方案可能比这更简单。跨度>
      • 你可能是对的。我必须进行更多的 C 编码,我想我将继续我的解决方案。性能还不错,令人惊讶。
      猜你喜欢
      • 2015-08-18
      • 1970-01-01
      • 1970-01-01
      • 2010-11-09
      • 2011-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多