Skip to content

Instantly share code, notes, and snippets.

@ericek111
Last active May 23, 2024 05:38
Show Gist options
  • Save ericek111/774a1661be69387de846f5f5a5977a46 to your computer and use it in GitHub Desktop.
Save ericek111/774a1661be69387de846f5f5a5977a46 to your computer and use it in GitHub Desktop.
X11 overlay
/*
* Copyright (c) 2020 ericek111 <erik.brocko@letemsvetemapplem.eu>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <stdlib.h>
#include <climits>
#include <chrono>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xfixes.h>
#include <math.h>
// Events for normal windows
#define BASIC_EVENT_MASK (StructureNotifyMask|ExposureMask|PropertyChangeMask|EnterWindowMask|LeaveWindowMask|KeyPressMask|KeyReleaseMask|KeymapStateMask)
#define NOT_PROPAGATE_MASK (KeyPressMask|KeyReleaseMask|ButtonPressMask|ButtonReleaseMask|PointerMotionMask|ButtonMotionMask)
using namespace std;
Display *g_display;
int g_screen;
Window g_win;
int g_disp_width;
int g_disp_height;
Pixmap g_bitmap;
Colormap g_colormap;
XColor red;
XColor black;
XColor white;
std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
int fpsmeterc = 0;
#define FPSMETERSAMPLE 100
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
string fpsstring = "";
int shape_event_base;
int shape_error_base;
// The window size
int WIDTH = 1920;
int HEIGHT = 1080;
long event_mask = (StructureNotifyMask|ExposureMask|PropertyChangeMask|EnterWindowMask|LeaveWindowMask|KeyRelease | ButtonPress|ButtonRelease|KeymapStateMask);
void allow_input_passthrough (Window w) {
XserverRegion region = XFixesCreateRegion (g_display, NULL, 0);
//XFixesSetWindowShapeRegion (g_display, w, ShapeBounding, 0, 0, 0);
XFixesSetWindowShapeRegion (g_display, w, ShapeInput, 0, 0, region);
XFixesDestroyRegion (g_display, region);
}
void list_fonts() {
char **fontlist;
int num_fonts;
fontlist = XListFonts (g_display, "*", 1000, &num_fonts);
for (int i = 0; i < num_fonts; ++i) {
fprintf(stderr, "> %s\n", fontlist[i]);
}
}
// Create a XColor from 3 byte tuple (0 - 255, 0 - 255, 0 - 255).
XColor createXColorFromRGB(short red, short green, short blue) {
XColor color;
// m_color.red = red * 65535 / 255;
color.red = (red * 0xFFFF) / 0xFF;
color.green = (green * 0xFFFF) / 0xFF;
color.blue = (blue * 0xFFFF) / 0xFF;
color.flags = DoRed | DoGreen | DoBlue;
if (!XAllocColor(g_display, DefaultColormap(g_display, g_screen), &color)) {
std::cerr << "createXColorFromRGB: Cannot create color" << endl;
exit(-1);
}
return color;
}
// Create a XColor from 3 byte tuple (0 - 255, 0 - 255, 0 - 255).
XColor createXColorFromRGBA(short red, short green, short blue, short alpha) {
XColor color;
// m_color.red = red * 65535 / 255;
color.red = (red * 0xFFFF) / 0xFF;
color.green = (green * 0xFFFF) / 0xFF;
color.blue = (blue * 0xFFFF) / 0xFF;
color.flags = DoRed | DoGreen | DoBlue;
if (!XAllocColor(g_display, DefaultColormap(g_display, g_screen), &color)) {
std::cerr << "createXColorFromRGB: Cannot create color" << endl;
exit(-1);
}
*(&color.pixel) = ((*(&color.pixel)) & 0x00ffffff) | (alpha << 24);
return color;
}
// Create a window
void createShapedWindow() {
XSetWindowAttributes wattr;
XColor bgcolor = createXColorFromRGBA(0, 0, 0, 0);
Window root = DefaultRootWindow(g_display);
Visual *visual = DefaultVisual(g_display, g_screen);
XVisualInfo vinfo;
XMatchVisualInfo(g_display, DefaultScreen(g_display), 32, TrueColor, &vinfo);
g_colormap = XCreateColormap(g_display, DefaultRootWindow(g_display), vinfo.visual, AllocNone);
XSetWindowAttributes attr;
attr.background_pixmap = None;
attr.background_pixel = bgcolor.pixel;
attr.border_pixel=0;
attr.win_gravity=NorthWestGravity;
attr.bit_gravity=ForgetGravity;
attr.save_under=1;
attr.event_mask=BASIC_EVENT_MASK;
attr.do_not_propagate_mask=NOT_PROPAGATE_MASK;
attr.override_redirect=1; // OpenGL > 0
attr.colormap = g_colormap;
//unsigned long mask = CWBackPixel|CWBorderPixel|CWWinGravity|CWBitGravity|CWSaveUnder|CWEventMask|CWDontPropagate|CWOverrideRedirect;
unsigned long mask = CWColormap | CWBorderPixel | CWBackPixel | CWEventMask | CWWinGravity|CWBitGravity | CWSaveUnder | CWDontPropagate | CWOverrideRedirect;
g_win = XCreateWindow(g_display, root, 0, 0, WIDTH, HEIGHT, 0, vinfo.depth, InputOutput, vinfo.visual, mask, &attr);
g_bitmap = XCreateBitmapFromData (g_display, RootWindow(g_display, g_screen), (char *)myshape_bits, myshape_width, myshape_height);
//XShapeCombineMask(g_display, g_win, ShapeBounding, 900, 500, g_bitmap, ShapeSet);
XShapeCombineMask(g_display, g_win, ShapeInput, 0, 0, None, ShapeSet );
// We want shape-changed event too
#define SHAPE_MASK ShapeNotifyMask
XShapeSelectInput (g_display, g_win, SHAPE_MASK );
// Tell the Window Manager not to draw window borders (frame) or title.
wattr.override_redirect = 1;
XChangeWindowAttributes(g_display, g_win, CWOverrideRedirect, &wattr);
allow_input_passthrough(g_win);
// Show the window
XMapWindow(g_display, g_win);
red = createXColorFromRGBA(255, 0, 0, 255);
black = createXColorFromRGBA(0, 0, 0, 200);
white = createXColorFromRGBA(255, 255, 255, 255);
}
// Draw on the shaped window.
// Yes it's possible, but only pixels that hits the mask are visible.
// A hint: You can change the mask during runtime if you like.
void draw()
{
fpsmeterc++;
if(fpsmeterc == FPSMETERSAMPLE) {
fpsmeterc = 0;
t1 = t2;
t2 = std::chrono::high_resolution_clock::now();
duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();
fpsstring = /*to_string(duration) + " / " +*/ to_string(1000000*FPSMETERSAMPLE/duration);
}
GC gc;
XGCValues gcv;
/*// Line width and type
gcv.line_width = 1;
gcv.line_style = LineSolid;
gcv.foreground = red.pixel;
unsigned long mask = GCLineWidth | GCLineStyle | GCForeground;
gc = XCreateGC(g_display, g_win, mask, &gcv);*/
gc = XCreateGC (g_display, g_win, 0, 0);
XSetBackground (g_display, gc, white.pixel);
XSetForeground (g_display, gc, red.pixel);
XFontStruct * font;
// const char * fontname = "-misc-fixed-bold-r-normal--18-120-100-100-c-90-iso8859-2";
// const char * fontname = "rk24"; // ~ chinese shit
// list_fonts();
const char * fontname = "9x15bold";
font = XLoadQueryFont (g_display, fontname);
/* If the font could not be loaded, revert to the "fixed" font. */
if (!font) {
fprintf (stderr, "unable to load font %s > using fixed\n", fontname);
font = XLoadQueryFont (g_display, "fixed");
}
XSetFont (g_display, gc, font->fid);
XSetForeground (g_display, gc, black.pixel);
XFillRectangle(g_display, g_win, gc, 0, 0, 250, 30);
XSetForeground (g_display, gc, red.pixel);
if(duration > 0.0f) {
const char * text = fpsstring.c_str();
XDrawString(g_display, g_win, gc, 10, 20, text, strlen(text));
}
XFreeGC(g_display, gc);
}
void openDisplay() {
g_display = XOpenDisplay(0);
if (!g_display) {
cerr << "Failed to open X display" << endl;
exit(-1);
}
g_screen = DefaultScreen(g_display);
g_disp_width = DisplayWidth(g_display, g_screen);
g_disp_height = DisplayHeight(g_display, g_screen);
// Has shape extions?
if (!XShapeQueryExtension (g_display, &shape_event_base, &shape_error_base)) {
cerr << "NO shape extension in your system !" << endl;
exit (-1);
}
}
int main() {
openDisplay();
createShapedWindow();
XEvent xevt;
XExposeEvent *eev;
XConfigureEvent *cev;
XKeyEvent *kev;
while (1)
{
/*XNextEvent(g_display, &xevt);
// Note! Shaped window generates some special events
// You got "shape_event_base" from XShapeQueryExtension(...)
if (xevt.type == shape_event_base + ShapeNotify)
{
cout << "Got shape changed event" << endl;
continue;
}
switch (xevt.type)
{
case Expose:
if (xevt.xexpose.count != 0) continue;
eev = &xevt.xexpose;
draw();
break;
case KeyPress:
kev = &xevt.xkey;
exit(0);
break;
case ConfigureNotify:
cev = &xevt.xconfigure;
break;
}*/
draw();
usleep(1000);
}
return 0;
}
@sersorrel
Copy link

@ericek111 hi, would you be interested in adding an open-source license to this code? I'd like to base a project off this program, but without a license I can't legally do that.

@ericek111
Copy link
Author

Hey. Thanks for respecting the spirit of open-source! I've licensed the code under GPLv3. If that does not suit you, we can arrange something else, though GPL is preferred.

@sersorrel
Copy link

Awesome, thanks! GPL is fine.

@liangqi
Copy link

liangqi commented Jan 13, 2021

xdrawoverlay.c: In function ‘void createShapedWindow()’:
xdrawoverlay.c:145:88: error: ‘myshape_bits’ was not declared in this scope
145 | g_bitmap = XCreateBitmapFromData (g_display, RootWindow(g_display, g_screen), (char *)myshape_bits, myshape_width, myshape_height);
| ^~~~~~~~~~~~
xdrawoverlay.c:145:102: error: ‘myshape_width’ was not declared in this scope
145 | g_bitmap = XCreateBitmapFromData (g_display, RootWindow(g_display, g_screen), (char *)myshape_bits, myshape_width, myshape_height);
| ^~~~~~~~~~~~~
xdrawoverlay.c:145:117: error: ‘myshape_height’ was not declared in this scope
145 | g_bitmap = XCreateBitmapFromData (g_display, RootWindow(g_display, g_screen), (char *)myshape_bits, myshape_width, myshape_height);
| ^~~~~~~~~~~~~~

@alpheratz0
Copy link

@liangqi g_bitmap is not used anywhere, so you can remove lines 145 and 41 without problem, then compile with

g++ overlay.cpp -o overlay -lX11 -lXext -lm -lXfixes

@simonfrancis
Copy link

Thanks for a very helpful example. I think before line 221 (XFreeGC()) you need:

XFreeFont(g_display, font);

to avoid leaking the font each time the draw loop is called.

@yoshisaac
Copy link

This has been extremely useful. I need an external overlay for a program I'm making, and it has been almost impossible to make a X window which does exactly this.
I'd give it a star if it wasn't a gist.

@8dcc
Copy link

8dcc commented May 17, 2024

In case anyone is interested, here is a C fork with some extra stuff: Link.

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