Select Git revision
-
Jiří Kalvoda authoredJiří Kalvoda authored
display.c 21.20 KiB
/*
* On-screen Display
*
* (c) 2013--2014 Martin Mares <mj@ucw.cz>
* (c) 2020--2021 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
*
* This code is heavily inspired by the libxosd library,
* which is (c) 2000, 2001 Andre Renaud <andre@ignavus.net>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/render.h>
#include <X11/Xft/Xft.h>
#include <getopt.h>
#undef DEBUG
#include "display.h"
#include "util.h"
#define SLIDERS_WITH_BRACKETS
struct display_state;
struct display_line {
struct osd_line * line;
// Used internally
int width;
int height;
int x_pos;
int y_pos;
int slider_unit;
int slider_space;
int slider_units;
};
struct osd_abstract display_state_new_arg(int argc, char ** argv,Display * dpy);
struct osd_abstract display_state_new(Display *dpy,int width,int height,int x,int y, char * font_name, double line_spacing);
void display_free(struct display_state *display);
void display_set_font(struct display_state *display, char *font_name, double line_spacing);
struct display_line *display_add_line(struct display_state *display, struct osd_line * line);
void display_show(struct display_state *display, struct osd_line * lines, int num_lines);
void display_hide(struct display_state *display);
void display_clear(struct display_state *display);
bool display_handle_event(struct display_state *display, XEvent *ev);
struct display_state {
// Basic characteristics of current display and screen
Display *dpy;
int screen;
Visual *visual;
Colormap cmap;
Window root;
int depth;
int screen_width;
int screen_height;
int screen_x;
int screen_y;
// Our window
Window win;
int win_width;
int win_height;
Pixmap mask_bitmap;
Pixmap image_pixmap;
GC gc;
GC mask_gc;
// Xft state
XftFont *font;
XftDraw *mask_draw;
XftDraw *image_draw;
// Contents of the display
struct display_line *lines;
int num_lines;
int max_lines;
int line_distance;
int line_height;
int line_skip;
bool visible;
// Used temporarily when drawing a line
XftColor fg_color;
XftColor mask_color;
};
static void
stay_on_top(struct display_state *display)
{
int format;
unsigned long nitems, bytes_after;
unsigned char *prop = NULL;
Atom type;
// Gnome-compliant way
Atom gnome = XInternAtom(display->dpy, "_WIN_SUPPORTING_WM_CHECK", False);
if (XGetWindowProperty(display->dpy, display->root, gnome, 0, 16384, False, AnyPropertyType,
&type, &format, &nitems, &bytes_after, &prop) == Success &&
nitems > 0)
{
DBG("stay_on_top: Gnome mode\n");
// FIXME: Check capabilities
XClientMessageEvent e;
memset(&e, 0, sizeof(e));
e.type = ClientMessage;
e.window = display->win;
e.message_type = XInternAtom(display->dpy, "_WIN_LAYER", False);
e.format = 32;
e.data.l[0] = 6; // WIN_LAYER_ONTOP */
XSendEvent(display->dpy, display->root, False, SubstructureNotifyMask, (XEvent *) &e);
XFree(prop);
return;
}
// NetWM-compliant way
Atom net_wm = XInternAtom(display->dpy, "_NET_SUPPORTED", False);
if (XGetWindowProperty(display->dpy, display->root, net_wm, 0, 16384, False, AnyPropertyType,
&type, &format, &nitems, &bytes_after, &prop) == Success &&
nitems > 0)
{
DBG("stay_on_top: NetWM mode\n");
XEvent e;
memset(&e, 0, sizeof(e));
e.xclient.type = ClientMessage;
e.xclient.message_type = XInternAtom(display->dpy, "_NET_WM_STATE", False);
e.xclient.display = display->dpy;
e.xclient.window = display->win;
e.xclient.format = 32;
e.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
e.xclient.data.l[1] = XInternAtom(display->dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
XSendEvent(display->dpy, display->root, False, SubstructureRedirectMask, &e);
XFree(prop);
return;
}
DBG("stay_on_top: WM does not support any known protocol\n");
}
// ****************************************************************************************
static
void display_state_new_arg_help(FILE * f)
{
fprintf(f,"Module DISPLAY help:\n\
-f, --font=<f>\t\tFont to use for the OSD\n\
-s, --line-spacing=<n>\tSet line spacing factor (decimal fraction, default=0.2)\n\
-x, --x=<px>\t\tLeft up corner positing (move right, default=0)\n\
-y, --y=<px>\t\tLeft up corner positing (move down, default=0)\n\
-w, --width=<px>\tDisplay width (default screen size)\n\
-h, --height=<px>\tDisplay height (default screen size)\n\
\n");
}
struct osd_abstract display_state_new_arg(int argc, char ** argv,Display *dpy)
{
static const char short_opts[] = "f:s:x:y:h:w:";
static const struct option long_opts[] = {
{ "font", required_argument, NULL, 'f' },
{ "line-spacing", required_argument, NULL, 's' },
{ "x", required_argument, NULL, 'x' },
{ "y", required_argument, NULL, 'y' },
{ "width", required_argument, NULL, 'h' },
{ "height", required_argument, NULL, 'w' },
{ NULL, 0, NULL, 0 },
};
//fprintf(stderr,"NEW DISPLAY:\n");
//for(int i=0;i<argc;i++) fprintf(stderr,"\t%s\n",argv[i]);
char *font_name = "times-64:bold";
double line_spacing = 0.2;
int opt;
int x=0,y=0;
int width = XDisplayWidth(dpy, XDefaultScreen(dpy));
int height = XDisplayHeight(dpy, XDefaultScreen(dpy));
optind = 0;
while (argv && argc>0 && (opt = getopt_long(argc+1, argv-1, short_opts, long_opts, NULL)) >= 0)
{
switch (opt)
{
case 'f':
font_name = optarg;
fprintf(stderr,"FONT SET\n");
break;
case 's':
line_spacing = atof(optarg);
break;
case 'x':
x = atoi(optarg);
break;
case 'y':
y = atoi(optarg);
break;
case 'w':
width = atoi(optarg);
break;
case 'h':
height = atoi(optarg);
break;
default:
display_state_new_arg_help(stderr);
exit(0);
}
}
return display_state_new(dpy,width,height,x,y,font_name,line_spacing);
}
struct osd_creator_abstract display_creator_new(void)
{
struct osd_creator_abstract r;
memset(&r, 0, sizeof(r));
r.name = "DISPLAY";
r.help = display_state_new_arg_help;
r.new.add_one_osd = display_state_new_arg;
return r;
}
// ****************************************************************************************
static
void display_state_new_by_outputs_help(FILE * f)
{
fprintf(f,"Module DISPLAY_BY_OUTPUTS help:\n\
-f, --font=<f>\t\tFont to use for the OSD\n\
-s, --line-spacing=<n>\tSet line spacing factor (decimal fraction, default=0.2)\n\
-n, --name=<s>\t\tname with expansion %%d to display number (expanded by printf)\n\
\n");
}
static
void display_state_new_by_outputs(struct osd_set *set, int argc, char ** argv,Display *dpy,int names_len, char ** names)
{
static const char short_opts[] = "f:s:n:";
static const struct option long_opts[] = {
{ "font", required_argument, NULL, 'f' },
{ "line-spacing", required_argument, NULL, 's' },
{ "name", required_argument, NULL, 'n' },
{ NULL, 0, NULL, 0 },
};
//fprintf(stderr,"NEW DISPLAY_BY_OUTPUTS:\n");
//for(int i=0;i<argc;i++) fprintf(stderr,"\t%s\n",argv[i]);
char * expanding_name = "";
char *font_name = "times-64:bold";
double line_spacing = 0.2;
int opt;
optind = 0;
while (argv && argc>0 && (opt = getopt_long(argc+1, argv-1, short_opts, long_opts, NULL)) >= 0)
{
switch (opt)
{
case 'f':
font_name = optarg;
fprintf(stderr,"FONT SET\n");
break;
case 's':
line_spacing = atof(optarg);
break;
case 'n':
expanding_name = optarg;
break;
default:
display_state_new_by_outputs_help(stderr);
exit(0);
}
}
FILE * f = popen("xrandr | grep \\ connected","r");
char line[1001];
char ** new_names = xmalloc(sizeof(char *)*(names_len+1));
for(int i=0;i<names_len;i++) new_names[i]=names[i];
new_names[names_len] = xmalloc(sizeof(char)*(strlen(expanding_name)+100));
int i=0;
while(fscanf(f," %1000[^\n]",line)==1)
{
int width,height,x,y;
char *l = line;
while(*l && !(*l==' ')) l++;
while(*l && !('0'<=*l&&*l<='9')) l++;
if(sscanf(l,"%dx%d+%d+%d",&width,&height,&x,&y)==4)
{
DBG("ADD screen %dx%d on %d,%d\n",width,height,x,y);
sprintf(new_names[names_len],expanding_name,i,i,i,i,i,i);
osd_set_add(set,display_state_new(dpy,width,height,x,y,font_name,line_spacing),names_len+(bool)expanding_name[0],new_names);
i++;
}
}
free(new_names[names_len]);
free(new_names);
}
struct osd_creator_abstract display_by_outputs_creator_new(void)
{
struct osd_creator_abstract r;
memset(&r, 0, sizeof(r));
r.name = "DISPLAY_BY_OUTPUTS";
r.new.add_multiple_osd = display_state_new_by_outputs;
r.help = display_state_new_by_outputs_help;
r.t = OSD_CEATE_TYPE_ADD_MULTIPLE_OSD;
return r;
}
// ****************************************************************************************
struct osd_abstract display_state_new(Display *dpy,int width,int height,int x,int y, char * font_name, double line_spacing)
{
struct display_state *display = xmalloc(sizeof(*display));
memset(display, 0, sizeof(*display));
display->dpy = dpy;
display->screen = XDefaultScreen(display->dpy);
display->visual = XDefaultVisual(display->dpy, display->screen);
display->cmap = DefaultColormap(display->dpy, display->screen);
display->root = DefaultRootWindow(display->dpy);
// FIXME: These can change. And what about Xinerama?
display->depth = XDefaultDepth(display->dpy, display->screen);
display->screen_width = width;
display->screen_height = height;
display->screen_x = x;
display->screen_y = y;
DBG("Screen: %dx%d depth %d\n", display->screen_width, display->screen_height, display->depth);
int event_basep, error_basep;
if (!XShapeQueryExtension(display->dpy, &event_basep, &error_basep))
die("XShape extension not supported by X server, giving up");
display->max_lines = 4;
display->num_lines = 0;
display->lines = xmalloc(sizeof(struct display_line) * display->max_lines);
display_set_font(display, font_name, line_spacing);
struct osd_abstract r;
memset(&r, 0, sizeof(r));
r.context = display;
void (*show)(struct display_state*, struct osd_line*, int) = display_show;
r.show = (void (*)(void*, struct osd_line*, int)) show;
void (*clear)(struct display_state*) = display_clear;
r.clear = (void (*)(void*)) clear;
bool (*handle_event)(struct display_state*, XEvent *) = display_handle_event;
r.handle_event = (bool (*)(void*, XEvent *)) handle_event;
return r;
}
void display_free(struct display_state *display)
{
display_hide(display);
if (display->font)
XftFontClose(display->dpy, display->font);
free(display->lines);
free(display);
}
void display_set_font(struct display_state *display, char *font_name, double line_spacing)
{
if (display->font)
XftFontClose(display->dpy, display->font);
DBG("Using font %s\n", font_name);
display->font = XftFontOpenName(display->dpy, display->screen, font_name);
if (!display->font)
die("Cannot open font %s", font_name);
DBG("Font: asc=%d desc=%d ht=%d\n", display->font->ascent, display->font->descent, display->font->height);
display->line_distance = display->font->height;
display->line_height = display->font->ascent;
display->line_skip = display->line_distance * line_spacing;
DBG("Line: distance=%d height=%d skip=%d\n", display->line_distance, display->line_height, display->line_skip);
}
struct display_line *display_add_line(struct display_state *display, struct osd_line * line)
{
if (display->num_lines >= display->max_lines)
{
display->max_lines = 2 * display->max_lines;
display->lines = xrealloc(display->lines, sizeof(struct display_line) * display->max_lines);
}
struct display_line *l = &display->lines[display->num_lines++];
l->line = line;
return l;
}
static void display_prepare_line(struct display_state *display, int i)
{
struct display_line *line = &display->lines[i];
switch (line->line->type)
{
case OSD_TYPE_TEXT:
{
XGlyphInfo gi;
XftTextExtentsUtf8(display->dpy, display->font, (unsigned char *) line->line->u.text, strlen(line->line->u.text), &gi);
DBG("Line #%d: Glyph info: (%d,%d)+(%d,%d) off (%d,%d)\n", i, gi.x, gi.y, gi.width, gi.height, gi.xOff, gi.yOff);
line->width = gi.width + 2*line->line->outline_width;
line->height = display->line_distance + 2*line->line->outline_width;
break;
}
case OSD_TYPE_PERCENTAGE:
case OSD_TYPE_SLIDER:
{
#ifdef SLIDERS_WITH_BRACKETS
line->slider_unit = display->line_height / 5;
line->slider_space = display->line_height / 5;
#else
line->slider_unit = display->line_height / 3;
line->slider_space = display->line_height / 6;
#endif
if (!line->slider_space)
line->slider_space = line->slider_unit = 1;
int use_width = display->screen_width * 4 / 5;
int u = line->slider_unit + line->slider_space;
line->slider_units = (use_width + line->slider_space) / u;
if (line->slider_units < 3)
line->slider_units = 3;
line->width = line->slider_units*u - line->slider_space + 2*line->line->outline_width;
line->height = display->line_height + 2*line->line->outline_width;
break;
}
default:
die("display_recalc_line: unknown type %d", line->line->type);
}
DBG("Line #%d: Width %d (outline %d)\n", i, line->width, line->line->outline_width);
}
static void display_justify_line(struct display_state *display, int i)
{
// FIXME: Support more modes of justification
struct display_line *line = &display->lines[i];
line->x_pos = (display->win_width - line->width) / 2;
DBG("Line #%d: Position (%d,%d)\n", i, line->x_pos, line->y_pos);
}
static void display_prepare(struct display_state *display)
{
display->win_width = 0;
display->win_height = 0;
for (int i=0; i < display->num_lines; i++)
{
struct display_line *line = &display->lines[i];
display_prepare_line(display, i);
if (line->width > display->win_width)
display->win_width = line->width;
display->win_height += line->height;
if (i)
display->win_height += display->line_skip;
}
if (display->win_width > display->screen_width)
display->win_width = display->screen_width;
if (display->win_height > display->screen_height)
display->win_height = display->screen_height;
DBG("Window size set to %dx%d\n", display->win_width, display->win_height);
int y = 0;
for (int i=0; i < display->num_lines; i++)
{
struct display_line *line = &display->lines[i];
if (i)
y += display->line_skip;
line->y_pos = y;
display_justify_line(display, i);
y += line->height;
}
}
static void display_draw_box(struct display_state *display, struct display_line *line, int x, int y, int w, int h)
{
XftDrawRect(display->mask_draw, &display->mask_color,
x - line->line->outline_width, y - line->line->outline_width,
w + 2*line->line->outline_width, h + 2*line->line->outline_width);
XftDrawRect(display->image_draw, &display->fg_color, x, y, w, h);
}
static void display_draw_line(struct display_state *display, int i)
{
struct display_line *line = &display->lines[i];
// Allocate colors
XftColor outline_color;
XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
if (!XftColorAllocName(display->dpy, display->visual, display->cmap, line->line->fg_color, &display->fg_color) ||
!XftColorAllocName(display->dpy, display->visual, display->cmap, line->line->outline_color, &outline_color) ||
!XftColorAllocValue(display->dpy, display->visual, display->cmap, &mask_rc, &display->mask_color))
die("Cannot allocate colors %s %s",line->line->fg_color,line->line->outline_color);
// Draw background in outline color
XftDrawRect(display->image_draw, &outline_color, 0, line->y_pos, display->win_width, line->height);
switch (line->line->type)
{
case OSD_TYPE_TEXT:
{
int x = line->x_pos + line->line->outline_width;
int y = line->y_pos + line->line->outline_width + display->line_height;
unsigned char *text = (unsigned char *) line->line->u.text;
int text_len = strlen(line->line->u.text);
XftDrawStringUtf8(display->image_draw, &display->fg_color, display->font, x, y, text, text_len);
// This is slow, but unlike the method used by libxosd, the result isn't ugly.
int outline = line->line->outline_width;
for (int dx = -outline; dx <= outline; dx++)
for (int dy = -outline; dy <= outline; dy++)
if (dx*dx + dy*dy <= outline*outline)
XftDrawStringUtf8(display->mask_draw, &display->mask_color, display->font, x + dx, y + dy, text, text_len);
break;
}
case OSD_TYPE_PERCENTAGE:
case OSD_TYPE_SLIDER:
{
int x = line->x_pos + line->line->outline_width;
int y = line->y_pos + line->line->outline_width;
int su = line->slider_unit;
int advance = su + line->slider_space;
int units = line->slider_units;
#ifdef SLIDERS_WITH_BRACKETS
units -= 2;
int hu = display->line_height / 5;
int hd = 2*hu;
display_draw_box(display, line, x, y, su - su/3, 5*hu);
display_draw_box(display, line, x, y, su, hu);
display_draw_box(display, line, x, y + 4*hu, su, hu);
x += advance;
#else
int hu = display->line_height / 3;
int hd = hu;
#endif
int min, max;
if (line->line->type == OSD_TYPE_PERCENTAGE)
{
min = 0;
max = (units+1) * line->line->u.percent / 100 - 1;
}
else
min = max = (units-1) * line->line->u.percent / 100;
for (int i=0; i < units; i++)
{
if (i >= min && i <= max)
display_draw_box(display, line, x, y, su, display->line_height);
else
display_draw_box(display, line, x, y + hd, su, hu);
x += advance;
}
#ifdef SLIDERS_WITH_BRACKETS
display_draw_box(display, line, x + su/3, y, su - su/3, 5*hu);
display_draw_box(display, line, x, y, su, hu);
display_draw_box(display, line, x, y + 4*hu, su, hu);
#endif
break;
}
default:
die("display_draw_line: unknown type %d", line->line->type);
}
XftColorFree(display->dpy, display->visual, display->cmap, &display->fg_color);
XftColorFree(display->dpy, display->visual, display->cmap, &outline_color);
XftColorFree(display->dpy, display->visual, display->cmap, &display->mask_color);
}
void display_show(struct display_state *display, struct osd_line * lines, int num_lines)
{
display_hide(display);
for(int i=0; i<num_lines; i++) display_add_line(display,lines+i);
display_prepare(display);
// Create our window
XSetWindowAttributes win_attr = {
.override_redirect = 1,
};
display->win = XCreateWindow(display->dpy,
display->root,
(display->screen_width - display->win_width) / 2 + display->screen_x, (display->screen_height - display->win_height) / 2 + display->screen_y,
display->win_width, display->win_height,
0,
display->depth,
CopyFromParent,
display->visual,
CWOverrideRedirect,
&win_attr);
XStoreName(display->dpy, display->win, "OSD");
stay_on_top(display);
// Create image pixmap and its graphic context
// display->gc can be used for both display->win and display->image_bitmap as they have the same root and depth
display->image_pixmap = XCreatePixmap(display->dpy, display->win, display->win_width, display->win_height, display->depth);
XGCValues gcv = {
.graphics_exposures = 0,
};
display->gc = XCreateGC(display->dpy, display->win, GCGraphicsExposures, &gcv);
// Create mask bitmap and its GC
display->mask_bitmap = XCreatePixmap(display->dpy, display->win, display->win_width, display->win_height, 1);
display->mask_gc = XCreateGC(display->dpy, display->mask_bitmap, GCGraphicsExposures, &gcv);
// Clear the mask bitmap
XSetBackground(display->dpy, display->mask_gc, WhitePixel(display->dpy, display->screen));
XSetForeground(display->dpy, display->mask_gc, BlackPixel(display->dpy, display->screen));
XFillRectangle(display->dpy, display->mask_bitmap, display->mask_gc, 0, 0, display->win_width, display->win_height);
// Create XftDraw for mask and image
display->mask_draw = XftDrawCreateBitmap(display->dpy, display->mask_bitmap);
display->image_draw = XftDrawCreate(display->dpy, display->image_pixmap, display->visual, display->cmap);
if (!display->mask_draw || !display->image_draw)
die("Cannot create XftDraw");
// Draw individial lines
for (int i=0; i < display->num_lines; i++)
display_draw_line(display, i);
XShapeCombineMask(display->dpy, display->win, ShapeBounding, 0, 0, display->mask_bitmap, ShapeSet);
XSelectInput(display->dpy, display->win, ExposureMask);
XMapRaised(display->dpy, display->win);
XFlush(display->dpy);
display->visible = 1;
}
void display_hide(struct display_state *display)
{
if (!display->visible)
return;
XftDrawDestroy(display->image_draw);
XftDrawDestroy(display->mask_draw);
XFreeGC(display->dpy, display->gc);
XFreeGC(display->dpy, display->mask_gc);
XFreePixmap(display->dpy, display->image_pixmap);
XFreePixmap(display->dpy, display->mask_bitmap);
XDestroyWindow(display->dpy, display->win);
XFlush(display->dpy);
display->visible = 0;
}
void display_clear(struct display_state *display)
{
display_hide(display);
display->num_lines = 0;
}
bool display_handle_event(struct display_state *display, XEvent *ev)
{
if (!display->visible)
return 0;
if (ev->type == Expose)
{
XExposeEvent *ex = &ev->xexpose;
if (ex->window == display->win)
{
DBG("Expose cnt=%d (%d,%d)+(%d,%d)\n", ex->count, ex->x, ex->y, ex->width, ex->height);
XCopyArea(display->dpy, display->image_pixmap, display->win, display->gc, ex->x, ex->y, ex->width, ex->height, ex->x, ex->y);
return 1;
}
}
return 0;
}