Skip to content

Instantly share code, notes, and snippets.

@richard-to
Created April 7, 2014 10:30
Show Gist options
  • Save richard-to/10017943 to your computer and use it in GitHub Desktop.
Save richard-to/10017943 to your computer and use it in GitHub Desktop.
X11 Example code for grabbing screenshots of window and sending key events
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
using namespace cv;
using namespace std;
#define ACTIVE_WINDOWS "_NET_CLIENT_LIST"
#define WINDOW_TITLE "Wolf3d Screenshot"
#define WINDOW_DOXBOX "DOSBox"
// Find the DOSBox Window so we can do cool stuff with it!
bool findDOSBoxWindow(Display *display, Window &window)
{
bool found = false;
Window rootWindow = RootWindow(display, DefaultScreen(display));
Atom atom = XInternAtom(display, ACTIVE_WINDOWS, true);
Atom actualType;
int format;
unsigned long numItems;
unsigned long bytesAfter;
unsigned char *data = '\0';
Window *list;
char *windowName;
int status = XGetWindowProperty(display, rootWindow, atom, 0L, (~0L), false,
AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, &data);
list = (Window *)data;
if (status >= Success && numItems) {
for (int i = 0; i < numItems; ++i) {
status = XFetchName(display, list[i], &windowName);
if (status >= Success) {
string windowNameStr(windowName);
if (windowNameStr.find(WINDOW_DOXBOX) == 0) {
window = list[i];
found = true;
break;
}
}
}
}
XFree(windowName);
XFree(data);
return found;
}
int main(int argc, char *argv[])
{
Display *display = XOpenDisplay(NULL);
Window rootWindow = RootWindow(display, DefaultScreen(display));
Window DOSBoxWindow;
XWindowAttributes DOSBoxWindowAttributes;
if (findDOSBoxWindow(display, DOSBoxWindow) == false) {
printf("Error: Cannot find DOSBox window. Exiting program.");
return 0;
}
XGetWindowAttributes(display, DOSBoxWindow, &DOSBoxWindowAttributes);
int width = DOSBoxWindowAttributes.width;
int height = DOSBoxWindowAttributes.height;
namedWindow(WINDOW_TITLE, WINDOW_AUTOSIZE);
Mat frame = Mat::zeros(height, width, CV_8UC3);
Vec3b frameRGB;
XColor colors;
XImage *image;
unsigned long red_mask;
unsigned long green_mask;
unsigned long blue_mask;
while (true) {
image = XGetImage(
display, DOSBoxWindow, 0, 0, width, height, AllPlanes, ZPixmap);
red_mask = image->red_mask;
green_mask = image->green_mask;
blue_mask = image->blue_mask;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
colors.pixel = XGetPixel(image, j, i);
// TODO(richard-to): Figure out why red and blue are swapped
frameRGB = frame.at<Vec3b>(i, j);
frameRGB.val[0] = colors.pixel & blue_mask;
frameRGB.val[1] = (colors.pixel & green_mask) >> 8;
frameRGB.val[2] = (colors.pixel & red_mask) >> 16;
frame.at<Vec3b>(i, j) = frameRGB;
}
}
XFree(image);
imshow(WINDOW_TITLE, frame);
if (waitKey(10) >= 0) {
break;
}
}
}
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
using namespace cv;
using namespace std;
#define ACTIVE_WINDOWS "_NET_CLIENT_LIST"
#define WINDOW_TITLE "Wolf3d Screenshot"
#define WINDOW_DOXBOX "DOSBox"
// Find the DOSBox Window so we can do cool stuff with it!
bool findDOSBoxWindow(Display *display, Window &window)
{
bool found = false;
Window rootWindow = RootWindow(display, DefaultScreen(display));
Atom atom = XInternAtom(display, ACTIVE_WINDOWS, true);
Atom actualType;
int format;
unsigned long numItems;
unsigned long bytesAfter;
unsigned char *data = '\0';
Window *list;
char *windowName;
int status = XGetWindowProperty(display, rootWindow, atom, 0L, (~0L), false,
AnyPropertyType, &actualType, &format, &numItems, &bytesAfter, &data);
list = (Window *)data;
if (status >= Success && numItems) {
for (int i = 0; i < numItems; ++i) {
status = XFetchName(display, list[i], &windowName);
if (status >= Success) {
string windowNameStr(windowName);
if (windowNameStr.find(WINDOW_DOXBOX) == 0) {
window = list[i];
found = true;
break;
}
}
}
}
XFree(windowName);
XFree(data);
return found;
}
void millisleep(int ms)
{
usleep(ms * 1000);
}
int main(int argc, char *argv[])
{
Display *display = XOpenDisplay(NULL);
Window rootWindow = RootWindow(display, DefaultScreen(display));
Window DOSBoxWindow;
XWindowAttributes DOSBoxWindowAttributes;
if (findDOSBoxWindow(display, DOSBoxWindow) == false) {
printf("Error: Cannot find DOSBox window. Exiting program.");
return 0;
}
XKeyEvent event;
event.type = KeyPress;
event.display = display;
event.send_event = False;
event.window = DOSBoxWindow;
event.root = rootWindow;
event.time = CurrentTime;
event.same_screen = True;
event.keycode = XKeysymToKeycode(display, XK_Up);
XSendEvent(display, DOSBoxWindow, True, KeyPressMask, (XEvent *)&event);
XFlush(display);
millisleep(100);
event.type = KeyRelease;
XSendEvent(display, DOSBoxWindow, True, KeyReleaseMask, (XEvent *)&event);
XFlush(display);
}
@letyrodridc
Copy link

Hi Richard, amazing work! Thanks so much for sharing. Notice that "// TODO(richard-to): Figure out why red and blue are swapped" I think the reason is that OpenCV works in BGR not RGB. Something like that, the same happens when reading with OpenCV and using MatPlotLib for displaying. Best.

@mister-good-deal
Copy link

mister-good-deal commented Feb 21, 2023

For way better performances use the following:

XImage* image = XGetImage(display, DOSBoxWindow, 0, 0, width, height, AllPlanes, ZPixmap);

cv::Mat frame(height, width, CV_8UC4, image->data);

cv::cvtColor(frame, frame, cv::COLOR_BGRA2BGR);

Instead of

image = XGetImage(
            display, DOSBoxWindow, 0, 0, width, height, AllPlanes, ZPixmap);

        red_mask = image->red_mask;
        green_mask = image->green_mask;
        blue_mask = image->blue_mask;

        for (int i = 0; i < height; ++i) {
            for (int j = 0; j < width; ++j) {
                colors.pixel = XGetPixel(image, j, i);
               
                // TODO(richard-to): Figure out why red and blue are swapped
                frameRGB = frame.at<Vec3b>(i, j);            
                frameRGB.val[0] = colors.pixel & blue_mask;
                frameRGB.val[1] = (colors.pixel & green_mask) >> 8;
                frameRGB.val[2] = (colors.pixel & red_mask) >> 16;       
                frame.at<Vec3b>(i, j) = frameRGB;
            }
        }
    // ...

@shaggyrogers
Copy link

Thanks! You need to use XDestroyImage(image), not XFree(image), since the latter doesn't free the image data.

Also, instead of finding the window by name, it's easier to use xwininfo to get the ID and pass it as an argument, which you can then just cast to Window:

./screenshot_window $(xwininfo -int -name "Window Name" | grep -oP "(?<=Window id: )\d+")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment