Last active
May 23, 2024 05:38
-
-
Save ericek111/774a1661be69387de846f5f5a5977a46 to your computer and use it in GitHub Desktop.
X11 overlay
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2020 ericek111 <[email protected]>. | |
* | |
* 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; | |
} | |
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.
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
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.