Select Git revision
-
Martin Mareš authoredMartin Mareš authored
osdd.c 8.78 KiB
/*
* On-screen Display Daemon
*
* (c) 2010--2014 Martin Mares <mj@ucw.cz>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <getopt.h>
#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#undef DEBUG
#include "util.h"
#include "display.h"
static struct osd_state *osd;
static timestamp_t now;
/*** Options ***/
static char *font_name = "times-64:bold";
static char *default_color = "green";
static char *default_outline_color = "black";
static int default_outline_width = 2;
static int default_duration = 1000;
static int default_min_duration = 250;
static int debug_mode;
static int test_mode;
static double line_spacing = 0.2;
static const char short_opts[] = "c:d:Df:m:o:O:s:";
enum long_opt {
OPT_TEST = 256,
};
static const struct option long_opts[] = {
{ "color", required_argument, NULL, 'c' },
{ "debug", no_argument, NULL, 'D' },
{ "duration", required_argument, NULL, 'd' },
{ "font", required_argument, NULL, 'f' },
{ "min-duration", required_argument, NULL, 'm' },
{ "outline-color", required_argument, NULL, 'o' },
{ "outline-width", required_argument, NULL, 'O' },
{ "line-spacing", required_argument, NULL, 's' },
{ "test", no_argument, NULL, OPT_TEST }, // Undocumented test mode
{ NULL, 0, NULL, 0 },
};
static void NONRET
usage(void)
{
fprintf(stderr, "Usage: osdd <options>\n\n\
Options:\n\
-c, --color=<c>\t\tDefault color (#rgb, #rrggbb or a name from rgb.txt)\n\
-D, --debug\t\tDebugging mode (do not detach from the terminal)\n\
-d, --duration=<ms>\tDefault message duration in milliseconds\n\
-f, --font=<f>\t\tFont to use for the OSD\n\
-m, --min-duration=<ms>\tDefault minimum message duration in milliseconds\n\
-o, --outline-color=<c>\tDefault outline color\n\
-O, --outline-width=<n>\tDefault outline width (default=2)\n\
-s, --line-spacing=<n>\tSet line spacing factor (decimal fraction, default=0.2)\n\
");
exit(1);
}
static void
parse_opts(int argc, char **argv)
{
int opt;
while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) >= 0)
switch (opt)
{
case 'c':
default_color = optarg;
break;
case 'd':
default_duration = atoi(optarg);
break;
case 'D':
debug_mode = 1;
break;
case 'f':
font_name = optarg;
break;
case 'l':
line_spacing = atof(optarg);
break;
case 'm':
default_min_duration = atoi(optarg);
break;
case 'o':
default_outline_color = optarg;
break;
case 'O':
default_outline_width = atoi(optarg);
break;
case OPT_TEST:
test_mode = 1;
debug_mode = 1;
break;
default:
usage();
}
if (optind < argc)
usage();
}
/*** Displaying of messages ***/
struct msg {
struct msg *next;
timestamp_t min_light, max_light;
char text[1];
};
static void
display_msg(struct msg *msg)
{
DBG("## Displaying message\n");
msg->min_light = now + default_min_duration;
msg->max_light = now + default_duration;
char *fg_color = default_color;
char *outline_color = default_outline_color;
int outline_width = default_outline_width;
char *line = msg->text;
while (*line)
{
// The parser it destructive, but it does not do any harm, since we display each message only once.
char *nl = strchr(line, '\n');
*nl++ = 0;
char *key;
char *val = strchr(line, ':');
if (val)
{
key = line;
*val++ = 0;
}
else
{
key = "";
val = line;
}
DBG("\t%s:%s\n", key, val);
struct osd_line *l = NULL;
if (!key[0])
{
l = osd_add_line(osd, OSD_TYPE_TEXT);
sprintf(l->u.text, "%.*s", OSD_MAX_LINE_LEN, val);
}
else if (!strcmp(key, "percentage") || !strcmp(key, "percent"))
{
l = osd_add_line(osd, OSD_TYPE_PERCENTAGE);
l->u.percent = atoi(val);
}
else if (!strcmp(key, "slider"))
{
l = osd_add_line(osd, OSD_TYPE_SLIDER);
l->u.percent = atoi(val);
}
else if (!strcmp(key, "duration"))
msg->max_light = now + atoi(val);
else if (!strcmp(key, "min-duration"))
msg->min_light = now + atoi(val);
else if (!strcmp(key, "color"))
fg_color = val;
else if (!strcmp(key, "outline-color"))
outline_color = val;
else if (!strcmp(key, "outline-width"))
outline_width = atoi(val);
if (l)
{
l->fg_color = fg_color;
l->outline_color = outline_color;
l->outline_width = outline_width;
}
line = nl;
}
if (msg->min_light > msg->max_light)
msg->min_light = msg->max_light;
osd_show(osd);
}
static void
msg_to_stdout(struct msg *msg)
{
DBG("## Printing message to stdout\n");
char *line = msg->text;
int out_parametr_index=0;
while (*line)
{
// The parser it destructive, but it does not do any harm, since we display each message only once.
char *nl = strchr(line, '\n');
*nl++ = 0;
char *key;
char *val = strchr(line, ':');
if (val)
{
key = line;
*val++ = 0;
}
else
{
key = "";
val = line;
}
DBG("\t%s:%s\n", key, val);
if (!key[0])
{
printf(out_parametr_index++?" %s":"%s",val);
}
line = nl;
}
printf("\n");
fflush(stdout);
}
static void
hide_msg(struct msg *msg)
{
DBG("## Hiding message\n");
osd_clear(osd);
free(msg);
}
/*** The message queue ***/
static struct msg *current_msg, *first_msg, *last_msg;
static void
enqueue_msg(unsigned char *buf, int len)
{
DBG("Received: [%.*s]\n", len, buf);
if (!len || buf[len-1] != '\n')
return;
struct msg *msg = xmalloc(sizeof(*msg) + len);
memcpy(msg->text, buf, len);
msg->text[len] = 0;
char to[123];
*to=0;
char *line = msg->text;
while (*line)
{
char *nl = strchr(line, '\n');
*nl++=0;
char *key;
char *val = strchr(line, ':');
if (val)
{
key = line;
*val++ = 0;
}
else
{
key = "";
val = line;
}
DBG("\t%s:%s\n", key, val);
if (!strcmp(key, "to"))
{
if(strlen(val)<120)
strcpy(to,val);
}
val[-1]=':';
nl[-1]=10;
line = nl;
}
if(!strcmp(to,"stdout"))
{
msg_to_stdout(msg);
}
else
{
if (first_msg)
last_msg->next = msg;
else
first_msg = msg;
last_msg = msg;
msg->next = NULL;
}
}
static void
parse_input(unsigned char *buf, int len)
{
/* The property might contain several messages concatenated. Split them. */
while (len > 0)
{
if (buf[0] == '\n')
{
buf++, len--;
continue;
}
int i = 0;
while (i < len && (buf[i] != '\n' || (i && buf[i-1] != '\n')))
i++;
enqueue_msg(buf, i);
buf += i, len -= i;
}
}
static void
do_test(void)
{
unsigned char buf[4096];
int len = 0;
int c;
while ((c = read(0, buf + len, 4096 - len)) > 0)
len += c;
if (len)
enqueue_msg(buf, len);
}
/*** Main loop ***/
int
main(int argc, char **argv)
{
parse_opts(argc, argv);
setlocale(LC_CTYPE, "");
XInitThreads();
Display *dpy = XOpenDisplay(NULL);
if (!dpy)
die("Cannot open display");
Window win = DefaultRootWindow(dpy);
Atom pty = XInternAtom(dpy, "OSD_QUEUE", False);
if (!pty)
die("Cannot intern OSD_QUEUE atom");
if (!debug_mode)
{
pid_t pid = fork();
if (pid < 0)
die("Cannot fork: %m");
if (pid > 0)
return 0;
setsid();
}
if (test_mode)
{
do_test();
pty = 0;
}
else
{
XSelectInput(dpy, win, PropertyChangeMask);
XDeleteProperty(dpy, win, pty);
XFlush(dpy);
}
osd = osd_new(dpy);
osd_set_font(osd, font_name, line_spacing);
struct pollfd pfd = {
.fd = ConnectionNumber(dpy),
.events = POLLIN,
};
for (;;)
{
now = get_current_time();
timestamp_t wait_until = now - 1;
if (!current_msg && first_msg)
{
current_msg = first_msg;
first_msg = first_msg->next;
display_msg(current_msg);
}
if (current_msg)
{
if (first_msg)
wait_until = current_msg->min_light;
else
wait_until = current_msg->max_light;
if (wait_until <= now)
{
hide_msg(current_msg);
current_msg = NULL;
continue;
}
}
if (test_mode && !current_msg)
break;
DBG("... waiting for %d ms\n", (int)(wait_until - now));
poll(&pfd, 1, wait_until - now);
if (pfd.revents & POLLIN)
{
while (XPending(dpy))
{
XEvent ev;
XNextEvent(dpy, &ev);
if (osd_handle_event(osd, &ev))
continue;
if (ev.type != PropertyNotify)
continue;
XPropertyEvent *p = &ev.xproperty;
if (p->window == win && p->atom == pty)
{
Atom pty_type;
int pty_fmt;
unsigned long pty_items, pty_remains;
unsigned char *pty_buf = NULL;
XGetWindowProperty(dpy, win, pty, 0, 4096, True, XA_STRING, &pty_type, &pty_fmt, &pty_items, &pty_remains, &pty_buf);
if (pty_type == XA_STRING && pty_fmt == 8 && pty_items)
parse_input(pty_buf, pty_items);
if (pty_buf)
XFree(pty_buf);
}
}
}
}
}