Select Git revision
-
Jiří Kalvoda authored
This reverts commit 783395ad.
Jiří Kalvoda authoredThis reverts commit 783395ad.
display.c 16.37 KiB
/*
* On-screen Display
*
* (c) 2013--2014 Martin Mares <mj@ucw.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>
#undef DEBUG
#include "util.h"
#include "display.h"
#define SLIDERS_WITH_BRACKETS
struct osd_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;
// 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 osd_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 osd_state *osd)
{
int format;
unsigned long nitems, bytes_after;
unsigned char *prop = NULL;
Atom type;
// Gnome-compliant way
Atom gnome = XInternAtom(osd->dpy, "_WIN_SUPPORTING_WM_CHECK", False);
if (XGetWindowProperty(osd->dpy, osd->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 = osd->win;
e.message_type = XInternAtom(osd->dpy, "_WIN_LAYER", False);
e.format = 32;
e.data.l[0] = 6; // WIN_LAYER_ONTOP */
XSendEvent(osd->dpy, osd->root, False, SubstructureNotifyMask, (XEvent *) &e);
XFree(prop);
return;
}
// NetWM-compliant way
Atom net_wm = XInternAtom(osd->dpy, "_NET_SUPPORTED", False);
if (XGetWindowProperty(osd->dpy, osd->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(osd->dpy, "_NET_WM_STATE", False);
e.xclient.display = osd->dpy;
e.xclient.window = osd->win;
e.xclient.format = 32;
e.xclient.data.l[0] = 1; // _NET_WM_STATE_ADD
e.xclient.data.l[1] = XInternAtom(osd->dpy, "_NET_WM_STATE_STAYS_ON_TOP", False);
XSendEvent(osd->dpy, osd->root, False, SubstructureRedirectMask, &e);
XFree(prop);
return;
}
DBG("stay_on_top: WM does not support any known protocol\n");
}
struct osd_state *osd_new(Display *dpy)
{
struct osd_state *osd = xmalloc(sizeof(*osd));
memset(osd, 0, sizeof(*osd));
osd->dpy = dpy;
osd->screen = XDefaultScreen(osd->dpy);
osd->visual = XDefaultVisual(osd->dpy, osd->screen);
osd->cmap = DefaultColormap(osd->dpy, osd->screen);
osd->root = DefaultRootWindow(osd->dpy);
// FIXME: These can change. And what about Xinerama?
osd->depth = XDefaultDepth(osd->dpy, osd->screen);
osd->screen_width = XDisplayWidth(osd->dpy, osd->screen);
osd->screen_height = XDisplayHeight(osd->dpy, osd->screen);
DBG("Screen: %dx%d depth %d\n", osd->screen_width, osd->screen_height, osd->depth);
int event_basep, error_basep;
if (!XShapeQueryExtension(osd->dpy, &event_basep, &error_basep))
die("XShape extension not supported by X server, giving up");
osd->max_lines = 4;
osd->lines = xmalloc(sizeof(struct osd_line) * osd->max_lines);
return osd;
}
void osd_free(struct osd_state *osd)
{
osd_hide(osd);
if (osd->font)
XftFontClose(osd->dpy, osd->font);
free(osd->lines);
free(osd);
}
void osd_set_font(struct osd_state *osd, char *font_name, double line_spacing)
{
if (osd->font)
XftFontClose(osd->dpy, osd->font);
DBG("Using font %s\n", font_name);
osd->font = XftFontOpenName(osd->dpy, osd->screen, font_name);
if (!osd->font)
die("Cannot open font %s", font_name);
DBG("Font: asc=%d desc=%d ht=%d\n", osd->font->ascent, osd->font->descent, osd->font->height);
osd->line_distance = osd->font->height;
osd->line_height = osd->font->ascent;
osd->line_skip = osd->line_distance * line_spacing;
DBG("Line: distance=%d height=%d skip=%d\n", osd->line_distance, osd->line_height, osd->line_skip);
}
struct osd_line *osd_add_line(struct osd_state *osd, enum osd_line_type type)
{
if (osd->num_lines >= osd->max_lines)
{
osd->max_lines = 2 * osd->max_lines;
osd->lines = xrealloc(osd->lines, sizeof(struct osd_line) * osd->max_lines);
}
struct osd_line *l = &osd->lines[osd->num_lines++];
l->type = type;
l->fg_color = "green";
l->outline_color = "black";
l->outline_width = 0;
switch (l->type)
{
case OSD_TYPE_TEXT:
l->u.text[0] = 0;
break;
case OSD_TYPE_PERCENTAGE:
case OSD_TYPE_SLIDER:
l->u.percent = 0;
break;
default:
die("osd_add_line: unknown type %d", type);
}
return l;
}
static void osd_prepare_line(struct osd_state *osd, int i)
{
struct osd_line *line = &osd->lines[i];
switch (line->type)
{
case OSD_TYPE_TEXT:
{
XGlyphInfo gi;
XftTextExtentsUtf8(osd->dpy, osd->font, (unsigned char *) line->u.text, strlen(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->outline_width;
line->height = osd->line_distance + 2*line->outline_width;
break;
}
case OSD_TYPE_PERCENTAGE:
case OSD_TYPE_SLIDER:
{
#ifdef SLIDERS_WITH_BRACKETS
line->slider_unit = osd->line_height / 5;
line->slider_space = osd->line_height / 5;
#else
line->slider_unit = osd->line_height / 3;
line->slider_space = osd->line_height / 6;
#endif
if (!line->slider_space)
line->slider_space = line->slider_unit = 1;
int use_width = osd->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->outline_width;
line->height = osd->line_height + 2*line->outline_width;
break;
}
default:
die("osd_recalc_line: unknown type %d", line->type);
}
DBG("Line #%d: Width %d (outline %d)\n", i, line->width, line->outline_width);
}
static void osd_justify_line(struct osd_state *osd, int i)
{
// FIXME: Support more modes of justification
struct osd_line *line = &osd->lines[i];
line->x_pos = (osd->win_width - line->width) / 2;
DBG("Line #%d: Position (%d,%d)\n", i, line->x_pos, line->y_pos);
}
static void osd_prepare(struct osd_state *osd)
{
osd->win_width = 0;
osd->win_height = 0;
for (int i=0; i < osd->num_lines; i++)
{
struct osd_line *line = &osd->lines[i];
osd_prepare_line(osd, i);
if (line->width > osd->win_width)
osd->win_width = line->width;
osd->win_height += line->height;
if (i)
osd->win_height += osd->line_skip;
}
if (osd->win_width > osd->screen_width)
osd->win_width = osd->screen_width;
if (osd->win_height > osd->screen_height)
osd->win_height = osd->screen_height;
DBG("Window size set to %dx%d\n", osd->win_width, osd->win_height);
int y = 0;
for (int i=0; i < osd->num_lines; i++)
{
struct osd_line *line = &osd->lines[i];
if (i)
y += osd->line_skip;
line->y_pos = y;
osd_justify_line(osd, i);
y += line->height;
}
}
static void osd_draw_box(struct osd_state *osd, struct osd_line *line, int x, int y, int w, int h)
{
XftDrawRect(osd->mask_draw, &osd->mask_color,
x - line->outline_width, y - line->outline_width,
w + 2*line->outline_width, h + 2*line->outline_width);
XftDrawRect(osd->image_draw, &osd->fg_color, x, y, w, h);
}
static void osd_draw_line(struct osd_state *osd, int i)
{
struct osd_line *line = &osd->lines[i];
// Allocate colors
XftColor outline_color;
XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
if (!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->fg_color, &osd->fg_color) ||
!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->outline_color, &outline_color) ||
!XftColorAllocValue(osd->dpy, osd->visual, osd->cmap, &mask_rc, &osd->mask_color))
die("Cannot allocate colors");
// Draw background in outline color
XftDrawRect(osd->image_draw, &outline_color, 0, line->y_pos, osd->win_width, line->height);
switch (line->type)
{
case OSD_TYPE_TEXT:
{
int x = line->x_pos + line->outline_width;
int y = line->y_pos + line->outline_width + osd->line_height;
unsigned char *text = (unsigned char *) line->u.text;
int text_len = strlen(line->u.text);
XftDrawStringUtf8(osd->image_draw, &osd->fg_color, osd->font, x, y, text, text_len);
// This is slow, but unlike the method used by libxosd, the result isn't ugly.
int outline = 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(osd->mask_draw, &osd->mask_color, osd->font, x + dx, y + dy, text, text_len);
break;
}
case OSD_TYPE_PERCENTAGE:
case OSD_TYPE_SLIDER:
{
int x = line->x_pos + line->outline_width;
int y = line->y_pos + 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 = osd->line_height / 5;
int hd = 2*hu;
osd_draw_box(osd, line, x, y, su - su/3, 5*hu);
osd_draw_box(osd, line, x, y, su, hu);
osd_draw_box(osd, line, x, y + 4*hu, su, hu);
x += advance;
#else
int hu = osd->line_height / 3;
int hd = hu;
#endif
int min, max;
if (line->type == OSD_TYPE_PERCENTAGE)
{
min = 0;
max = (units+1) * line->u.percent / 100 - 1;
}
else
min = max = (units-1) * line->u.percent / 100;
for (int i=0; i < units; i++)
{
if (i >= min && i <= max)
osd_draw_box(osd, line, x, y, su, osd->line_height);
else
osd_draw_box(osd, line, x, y + hd, su, hu);
x += advance;
}
#ifdef SLIDERS_WITH_BRACKETS
osd_draw_box(osd, line, x + su/3, y, su - su/3, 5*hu);
osd_draw_box(osd, line, x, y, su, hu);
osd_draw_box(osd, line, x, y + 4*hu, su, hu);
#endif
break;
}
default:
die("osd_draw_line: unknown type %d", line->type);
}
XftColorFree(osd->dpy, osd->visual, osd->cmap, &osd->fg_color);
XftColorFree(osd->dpy, osd->visual, osd->cmap, &outline_color);
XftColorFree(osd->dpy, osd->visual, osd->cmap, &osd->mask_color);
}
static void repace_bad_char(char * in)
{
for(;*in;in++)
{
if(' '== *in) continue;
if('a'<= *in&& *in<='z') continue;
if('A'<= *in&& *in<='Z') continue;
if('0'<= *in&& *in<='9') continue;
//if(128 <= (unsigned char)*in) continue;
*in='?';
}
}
static void osd_log_to_file(struct osd_state *osd)
{
#define MAXIMUM_FILE_MANE_LEN 1024
int need_log=0;
for(int i=0;i<osd->num_lines;i++) need_log|=osd->lines[i].log;
if(need_log&1)
{
char name[MAXIMUM_FILE_MANE_LEN];
snprintf(name,MAXIMUM_FILE_MANE_LEN,"%s/.osdd_last",getenv("HOME"));
FILE * f = fopen(name,"w");
if(f)
{
fprintf(f,"%lld\n",(long long)time(0));
for(int i=0;i<osd->num_lines;i++)
{
struct osd_line * line = osd->lines+i;
if(!(line->log & 1)) continue;
if(line->type!=OSD_TYPE_TEXT) continue;
// Allocate colors
XftColor outline_color;
XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
if (!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->fg_color, &osd->fg_color) ||
!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->outline_color, &outline_color) ||
!XftColorAllocValue(osd->dpy, osd->visual, osd->cmap, &mask_rc, &osd->mask_color))
die("Cannot allocate colors");
typeof(osd->fg_color.color) c = osd->fg_color.color;
//fprintf(stderr,"%02x%02x%02x %s\n",c.red/256,c.green/256,c.blue/256,osd->lines[i].u.text);
char text[1024];
snprintf(text,1000,osd->lines[i].u.text);
repace_bad_char(text);
fprintf(f,"%02x%02x%02x %s\n",c.red/256,c.green/256,c.blue/256,text);
}
fclose(f);
}
}
if(need_log&2)
{
char name[MAXIMUM_FILE_MANE_LEN];
snprintf(name,MAXIMUM_FILE_MANE_LEN,"%s/.osdd_log",getenv("HOME"));
FILE * f = fopen(name,"a");
if(f)
{
time_t t = time(NULL);
struct tm tm = *localtime(&t);
fprintf(f,"%04d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
for(int i=0;i<osd->num_lines;i++)
{
struct osd_line * line = osd->lines+i;
if(!(line->log & 2)) continue;
// Allocate colors
XftColor outline_color;
XRenderColor mask_rc = { .red = 0xffff, .green = 0xffff, .blue = 0xffff, .alpha = 0xffff };
if (!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->fg_color, &osd->fg_color) ||
!XftColorAllocName(osd->dpy, osd->visual, osd->cmap, line->outline_color, &outline_color) ||
!XftColorAllocValue(osd->dpy, osd->visual, osd->cmap, &mask_rc, &osd->mask_color))
die("Cannot allocate colors");
typeof(osd->fg_color.color) c = osd->fg_color.color;
//fprintf(stderr,"%02x%02x%02x %s\n",c.red/256,c.green/256,c.blue/256,osd->lines[i].u.text);
fprintf(f," \x1b[38;2;%d;%d;%dm%s\e[0m",c.red/256,c.green/256,c.blue/256,osd->lines[i].u.text);
}
fprintf(f,"\n");
fclose(f);
}
}
}
void osd_show(struct osd_state *osd)
{
osd_hide(osd);
osd_prepare(osd);
osd_log_to_file(osd);
// Create our window
XSetWindowAttributes win_attr = {
.override_redirect = 1,
};
osd->win = XCreateWindow(osd->dpy,
osd->root,
(osd->screen_width - osd->win_width) / 2, (osd->screen_height - osd->win_height) / 2,
osd->win_width, osd->win_height,
0,
osd->depth,
CopyFromParent,
osd->visual,
CWOverrideRedirect,
&win_attr);
XStoreName(osd->dpy, osd->win, "OSD");
stay_on_top(osd);
// Create image pixmap and its graphic context
// osd->gc can be used for both osd->win and osd->image_bitmap as they have the same root and depth
osd->image_pixmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, osd->depth);
XGCValues gcv = {
.graphics_exposures = 0,
};
osd->gc = XCreateGC(osd->dpy, osd->win, GCGraphicsExposures, &gcv);
// Create mask bitmap and its GC
osd->mask_bitmap = XCreatePixmap(osd->dpy, osd->win, osd->win_width, osd->win_height, 1);
osd->mask_gc = XCreateGC(osd->dpy, osd->mask_bitmap, GCGraphicsExposures, &gcv);
// Clear the mask bitmap
XSetBackground(osd->dpy, osd->mask_gc, WhitePixel(osd->dpy, osd->screen));
XSetForeground(osd->dpy, osd->mask_gc, BlackPixel(osd->dpy, osd->screen));
XFillRectangle(osd->dpy, osd->mask_bitmap, osd->mask_gc, 0, 0, osd->win_width, osd->win_height);
// Create XftDraw for mask and image
osd->mask_draw = XftDrawCreateBitmap(osd->dpy, osd->mask_bitmap);
osd->image_draw = XftDrawCreate(osd->dpy, osd->image_pixmap, osd->visual, osd->cmap);
if (!osd->mask_draw || !osd->image_draw)
die("Cannot create XftDraw");
// Draw individial lines
for (int i=0; i < osd->num_lines; i++)
osd_draw_line(osd, i);
XShapeCombineMask(osd->dpy, osd->win, ShapeBounding, 0, 0, osd->mask_bitmap, ShapeSet);
XSelectInput(osd->dpy, osd->win, ExposureMask);
XMapRaised(osd->dpy, osd->win);
XFlush(osd->dpy);
osd->visible = 1;
}
void osd_hide(struct osd_state *osd)
{
if (!osd->visible)
return;
XftDrawDestroy(osd->image_draw);
XftDrawDestroy(osd->mask_draw);
XFreeGC(osd->dpy, osd->gc);
XFreeGC(osd->dpy, osd->mask_gc);
XFreePixmap(osd->dpy, osd->image_pixmap);
XFreePixmap(osd->dpy, osd->mask_bitmap);
XDestroyWindow(osd->dpy, osd->win);
XFlush(osd->dpy);
osd->visible = 0;
}
void osd_clear(struct osd_state *osd)
{
osd_hide(osd);
osd->num_lines = 0;
}
bool osd_handle_event(struct osd_state *osd, XEvent *ev)
{
if (!osd->visible)
return 0;
if (ev->type == Expose)
{
XExposeEvent *ex = &ev->xexpose;
if (ex->window == osd->win)
{
DBG("Expose cnt=%d (%d,%d)+(%d,%d)\n", ex->count, ex->x, ex->y, ex->width, ex->height);
XCopyArea(osd->dpy, osd->image_pixmap, osd->win, osd->gc, ex->x, ex->y, ex->width, ex->height, ex->x, ex->y);
return 1;
}
}
return 0;
}