diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2024-07-15 00:35:15 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2024-08-13 23:36:43 -0700 |
commit | 9b05c41168842035ddcd377ed5e23bb862fb4a60 (patch) | |
tree | 60c480f891ed2a256c7c91083e7385f6e8666a86 /src | |
parent | 94a7020ad8c9efd9c5818eb3422ff4cb66a1b278 (diff) |
charts: first stab at factoring out Xlib from charts/vmon
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 30 | ||||
-rw-r--r-- | src/ascii.c | 1228 | ||||
-rw-r--r-- | src/ascii.h | 9 | ||||
-rw-r--r-- | src/charts.c | 646 | ||||
-rw-r--r-- | src/charts.h | 24 | ||||
-rw-r--r-- | src/composite.c | 8 | ||||
-rw-r--r-- | src/vcr.c | 2176 | ||||
-rw-r--r-- | src/vcr.h | 95 | ||||
-rw-r--r-- | src/vmon.c | 293 | ||||
-rw-r--r-- | src/vwm.c | 12 | ||||
-rw-r--r-- | src/vwm.h | 2 |
11 files changed, 3806 insertions, 717 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 45428f9..a8d1035 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,10 +1,26 @@ SUBDIRS = libvmon -bin_PROGRAMS = vwm vmon +bin_PROGRAMS = vmon -vwm_SOURCES = clickety.c composite.c context.c desktop.c key.c launch.c logo.c charts.c screen.c vwm.c window.c xevent.c xserver.c xwindow.c clickety.h composite.h context.h desktop.h direction.h key.h launch.h list.h logo.h charts.h screen.h util.h vwm.h window.h xevent.h xserver.h xwindow.h colors.def context_colors.def launchers.def -vwm_LDADD = @VWM_LIBS@ libvmon/libvmon.a -vwm_CPPFLAGS = @VWM_CFLAGS@ +vmon_SOURCES = ascii.c charts.c vcr.c vmon.c ascii.h charts.h vcr.h +vmon_LDADD = libvmon/libvmon.a +vmon_CPPFLAGS = -O2 -vmon_SOURCES = vmon.c charts.c xserver.c charts.h xserver.h -vmon_LDADD= @VMON_LIBS@ libvmon/libvmon.a -vmon_CPPFLAGS = @VMON_CFLAGS@ +if HAVE_XCLIENT_DEV +vmon_SOURCES += xserver.c xserver.h +vmon_LDADD += @XCLIENT_DEV_LIBS@ +vmon_CPPFLAGS += @XCLIENT_DEV_CFLAGS@ -DUSE_XLIB +endif + +if HAVE_PNG_DEV +vmon_CPPFLAGS += @PNG_DEV_CFLAGS@ -DUSE_PNG +vmon_LDADD += @PNG_DEV_LIBS@ +endif + + +if ENABLE_VWM +bin_PROGRAMS += vwm + +vwm_SOURCES = ascii.c clickety.c composite.c context.c desktop.c key.c launch.c logo.c charts.c screen.c vcr.c vwm.c window.c xevent.c xserver.c xwindow.c ascii.h clickety.h composite.h context.h desktop.h direction.h key.h launch.h list.h logo.h charts.h screen.h util.h vcr.h vwm.h window.h xevent.h xserver.h xwindow.h colors.def context_colors.def launchers.def +vwm_LDADD = @XWM_DEV_LIBS@ libvmon/libvmon.a +vwm_CPPFLAGS = @XWM_DEV_CFLAGS@ -DUSE_XLIB +endif diff --git a/src/ascii.c b/src/ascii.c new file mode 100644 index 0000000..5e6183f --- /dev/null +++ b/src/ascii.c @@ -0,0 +1,1228 @@ +/* this comes from rototiller (gplv2) */ +#include "ascii.h" + +const char ascii_chars[256][ASCII_WIDTH * ASCII_HEIGHT] = { + ['!'] = { + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['"'] = { + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['#'] = { + 0, 0, 0, 0, 0, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 1, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['$'] = { + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 1, + 1, 0, 1, 0, 0, + 1, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 1, 0, 1, + 0, 0, 1, 0, 1, + 1, 1, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['%'] = { + 0, 1, 0, 0, 1, + 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 1, 0, + 1, 0, 1, 0, 1, + 1, 0, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['&'] = { + 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 1, 0, 0, + 1, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 1, 0, 0, + 1, 0, 0, 1, 1, + 1, 0, 0, 1, 0, + 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['\''] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['('] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + }, + [')'] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + }, + ['*'] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 1, 0, 1, 0, 1, + 0, 1, 1, 1, 0, + 1, 0, 1, 0, 1, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['+'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + [','] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['-'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['.'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['/'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['0'] = { + 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['1'] = { + 0, 0, 1, 0, 0, + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['2'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['3'] = { + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['4'] = { + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 1, 0, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 1, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['5'] = { + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 1, 1, 0, + 1, 1, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['6'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['7'] = { + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['8'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['9'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + + [':'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + [';'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['<'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + }, + ['='] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['>'] = { + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['?'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['@'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 1, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 1, 0, + 1, 0, 0, 0, 0, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['a'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 1, 1, + 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['b'] = { + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['c'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['d'] = { + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['e'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['f'] = { + 0, 0, 1, 1, 0, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['g'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + }, + ['h'] = { + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 1, 1, 0, + 1, 1, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['i'] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['j'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, + 1, 0, 0, 1, 0, + 0, 1, 1, 0, 0, + }, + ['k'] = { + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 1, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 1, 0, 1, 0, 0, + 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['l'] = { + 0, 1, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['m'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 0, 1, 0, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['n'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 1, 1, 0, + 1, 1, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['o'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['p'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + }, + ['q'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + }, + ['r'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 1, 1, 0, + 1, 1, 0, 0, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['s'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 0, 1, 1, 0, 0, + 0, 0, 0, 1, 0, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['t'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 1, 1, 1, 1, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 1, + 0, 0, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['u'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 1, 1, + 0, 1, 1, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['v'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['w'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['x'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['y'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 1, 1, + 0, 1, 1, 0, 1, + 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 1, + }, + ['z'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['A'] = { + 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['B'] = { + 1, 1, 1, 1, 0, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['C'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['D'] = { + 1, 1, 1, 1, 0, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 0, 1, 0, 0, 1, + 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['E'] = { + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['F'] = { + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['G'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['H'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['I'] = { + 0, 1, 1, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['J'] = { + 0, 0, 1, 1, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 1, 0, 0, 1, 0, + 0, 1, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['K'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 1, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 1, 0, 1, 0, 0, + 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['L'] = { + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['M'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 0, 1, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['N'] = { + 1, 0, 0, 0, 1, + 1, 1, 0, 0, 1, + 1, 1, 0, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 0, 1, 1, + 1, 0, 0, 1, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['O'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['P'] = { + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['Q'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 1, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, + }, + ['R'] = { + 1, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 1, 1, 1, 0, + 1, 0, 1, 0, 0, + 1, 0, 0, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['S'] = { + 0, 1, 1, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['T'] = { + 1, 1, 1, 1, 1, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['U'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['V'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['W'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 1, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['X'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['Y'] = { + 1, 0, 0, 0, 1, + 1, 0, 0, 0, 1, + 0, 1, 0, 1, 0, + 0, 1, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['Z'] = { + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + + ['['] = { + 0, 1, 1, 1, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 1, 1, 0, + }, + ['\\'] = { + 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + }, + [']'] = { + 0, 1, 1, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 1, 0, + 0, 1, 1, 1, 0, + }, + ['^'] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 1, 0, 1, 0, + 1, 0, 0, 0, 1, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['_'] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, + }, + ['`'] = { + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, + ['{'] = { + 0, 0, 0, 1, 1, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 1, + }, + ['|'] = { + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, + }, + ['}'] = { + 1, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 1, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + }, + ['~'] = { + 0, 1, 0, 0, 1, + 1, 0, 1, 0, 1, + 1, 0, 0, 1, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + }, +}; diff --git a/src/ascii.h b/src/ascii.h new file mode 100644 index 0000000..6c86898 --- /dev/null +++ b/src/ascii.h @@ -0,0 +1,9 @@ +#ifndef _ASCII_H +#define _ASCII_H + +#define ASCII_WIDTH 5 +#define ASCII_HEIGHT 11 + +extern const char ascii_chars[256][ASCII_WIDTH * ASCII_HEIGHT]; + +#endif diff --git a/src/charts.c b/src/charts.c index 8ebdc4b..c68ba0f 100644 --- a/src/charts.c +++ b/src/charts.c @@ -1,7 +1,7 @@ /* * \/\/\ * - * Copyright (C) 2012-2018 Vito Caputo - <vcaputo@pengaru.com> + * Copyright (C) 2012-2024 Vito Caputo - <vcaputo@pengaru.com> * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published @@ -18,28 +18,29 @@ /* libvmon integration, warning: this gets a little crazy especially in the rendering. */ -#include <X11/Xatom.h> -#include <X11/Xlib.h> -#include <X11/extensions/Xfixes.h> -#include <X11/extensions/Xrender.h> #include <assert.h> #include <math.h> #include <stdarg.h> #include <stdlib.h> #include <sys/time.h> +#ifdef USE_XLIB +#include <X11/extensions/Xfixes.h> +#endif + + #include "charts.h" -#include "composite.h" #include "libvmon/vmon.h" #include "list.h" -#include "vwm.h" +#include "util.h" +#include "vcr.h" + +#ifdef USE_XLIB +#include "composite.h" #include "xwindow.h" +#include "vwm.h" +#endif -#define CHART_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ -#define CHART_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" -#define CHART_ROW_HEIGHT 15 /* this should always be larger than the font height */ -#define CHART_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ -#define CHART_GRAPH_MIN_HEIGHT (4 * CHART_ROW_HEIGHT) #define CHART_ISTHREAD_ARGV "~" /* use this string to mark threads in the argv field */ #define CHART_NOCOMM_ARGV "#missed it!" /* use this string to substitute the command when missing in argv field */ #define CHART_MAX_ARGC 64 /* this is a huge amount */ @@ -49,7 +50,7 @@ /* the global charts state, supplied to vwm_chart_create() which keeps a reference for future use. */ typedef struct _vwm_charts_t { - vwm_xserver_t *xserver; /* xserver supplied to vwm_charts_init() */ + vcr_backend_t *vcr_backend; /* supplied to vwm_charts_create() */ /* libvmon */ struct timeval maybe_sample, last_sample, this_sample; @@ -60,17 +61,6 @@ typedef struct _vwm_charts_t { vmon_t vmon; float prev_sampling_interval, sampling_interval; int sampling_paused, contiguous_drops, primed; - - /* X */ - XFontStruct *chart_font; - GC text_gc; - Picture shadow_fill, - text_fill, - bg_fill, - snowflakes_text_fill, - grapha_fill, - graphb_fill, - finish_fill; } vwm_charts_t; typedef enum _vwm_column_type_t { @@ -113,19 +103,14 @@ typedef struct _vwm_column_t { /* everything needed by the per-window chart's context */ typedef struct _vwm_chart_t { vmon_proc_t *monitor; /* vmon process monitor handle */ - Pixmap text_pixmap; /* pixmap for charted text (kept around for XDrawText usage) */ - Picture text_picture; /* picture representation of text_pixmap */ - Picture shadow_picture; /* text shadow layer */ - Picture grapha_picture; /* graph A layer */ - Picture graphb_picture; /* graph B layer */ - Picture tmp_picture; /* 1 row worth of temporary picture space */ - Picture picture; /* chart picture derived from the pixmap, for render compositing */ - int width; /* current width of the chart */ - int height; /* current height of the chart */ + vcr_t *vcr; + + int hierarchy_end; /* row where the process hierarchy currently ends */ + + /* FIXME TODO: this is redundant with the same things in vcr_t now, dedupe them */ int visible_width; /* currently visible width of the chart */ int visible_height; /* currently visible height of the chart */ - int phase; /* current position within the (horizontally scrolling) graphs */ - int hierarchy_end; /* row where the process hierarchy currently ends */ + int snowflakes_cnt; /* count of snowflaked rows (reset to zero to truncate snowflakes display) */ int gen_last_composed; /* the last composed vmon generation */ int redraw_needed; /* if a redraw is required (like when the window is resized...) */ @@ -152,17 +137,6 @@ static float sampling_intervals[] = { .025, /* ~40Hz */ .01666}; /* ~60Hz */ -static XRenderColor chart_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, - chart_shadow_color = { 0x0000, 0x0000, 0x0000, 0xC000}, - chart_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, - chart_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, - chart_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, - chart_trans_color = {0x00, 0x00, 0x00, 0x00}, - chart_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ - chart_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ -static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; -static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; - /* wrapper around snprintf always returning the length of what's in the buf */ static int snpf(char *str, size_t size, const char *format, ...) @@ -212,66 +186,10 @@ static void vmon_dtor_cb(vmon_t *vmon, vmon_proc_t *proc) } -/* convenience helper for creating a pixmap */ -static Pixmap create_pixmap(vwm_charts_t *charts, unsigned width, unsigned height, unsigned depth) -{ - vwm_xserver_t *xserver = charts->xserver; - - return XCreatePixmap(xserver->display, XSERVER_XROOT(xserver), width, height, depth); -} - - -/* convenience helper for creating a picture, supply res_pixmap to keep a reference to the pixmap drawable. */ -static Picture create_picture(vwm_charts_t *charts, unsigned width, unsigned height, unsigned depth, unsigned long attr_mask, XRenderPictureAttributes *attr, Pixmap *res_pixmap) -{ - vwm_xserver_t *xserver = charts->xserver; - Pixmap pixmap; - Picture picture; - int format; - - /* FIXME this pixmap->picture dance seems silly, investigate further. TODO */ - switch (depth) { - case 8: - format = PictStandardA8; - break; - case 32: - format = PictStandardARGB32; - break; - default: - assert(0); - } - - pixmap = create_pixmap(charts, width, height, depth); - picture = XRenderCreatePicture(xserver->display, pixmap, XRenderFindStandardFormat(xserver->display, format), attr_mask, attr); - - if (res_pixmap) { - *res_pixmap = pixmap; - } else { - XFreePixmap(xserver->display, pixmap); - } - - return picture; -} - - -/* convenience helper for creating a filled picture, supply res_pixmap to keep a reference to the pixmap drawable. */ -static Picture create_picture_fill(vwm_charts_t *charts, unsigned width, unsigned height, unsigned depth, unsigned long attrs_mask, XRenderPictureAttributes *attrs, const XRenderColor *color, Pixmap *res_pixmap) -{ - vwm_xserver_t *xserver = charts->xserver; - Picture picture; - - picture = create_picture(charts, width, height, depth, attrs_mask, attrs, res_pixmap); - XRenderFillRectangle(xserver->display, PictOpSrc, picture, color, 0, 0, width, height); - - return picture; -} - - /* initialize charts system */ -vwm_charts_t * vwm_charts_create(vwm_xserver_t *xserver) +vwm_charts_t * vwm_charts_create(vcr_backend_t *vbe) { vwm_charts_t *charts; - Pixmap bitmask; charts = calloc(1, sizeof(vwm_charts_t)); if (!charts) { @@ -279,7 +197,7 @@ vwm_charts_t * vwm_charts_create(vwm_xserver_t *xserver) goto _err; } - charts->xserver = xserver; + charts->vcr_backend = vbe; charts->prev_sampling_interval = charts->sampling_interval = 0.1f; /* default to 10Hz */ if (!vmon_init(&charts->vmon, VMON_FLAG_2PASS, CHART_VMON_SYS_WANTS, CHART_VMON_PROC_WANTS)) { @@ -293,40 +211,8 @@ vwm_charts_t * vwm_charts_create(vwm_xserver_t *xserver) charts->vmon.sample_cb_arg = charts; gettimeofday(&charts->this_sample, NULL); - /* get all the text and graphics stuff setup for charts */ - charts->chart_font = XLoadQueryFont(xserver->display, CHART_FIXED_FONT); - if (!charts->chart_font) { - VWM_ERROR("unable to load chart font \"%s\"", CHART_FIXED_FONT); - goto _err_vmon; - } - - /* create a GC for rendering the text using Xlib into the text chart stencils */ - bitmask = create_pixmap(charts, 1, 1, CHART_MASK_DEPTH); - charts->text_gc = XCreateGC(xserver->display, bitmask, 0, NULL); - XSetForeground(xserver->display, charts->text_gc, WhitePixel(xserver->display, xserver->screen_num)); - XFreePixmap(xserver->display, bitmask); - - /* create some repeating source fill pictures for drawing through the text and graph stencils */ - charts->text_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_visible_color, NULL); - charts->shadow_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_shadow_color, NULL); - - charts->bg_fill = create_picture(charts, 1, CHART_ROW_HEIGHT, 32, CPRepeat, &pa_repeat, NULL); - XRenderFillRectangle(xserver->display, PictOpSrc, charts->bg_fill, &chart_bg_color, 0, 0, 1, CHART_ROW_HEIGHT); - XRenderFillRectangle(xserver->display, PictOpSrc, charts->bg_fill, &chart_div_color, 0, CHART_ROW_HEIGHT - 1, 1, 1); - - charts->snowflakes_text_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_snowflakes_visible_color, NULL); - charts->grapha_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_grapha_color, NULL); - charts->graphb_fill = create_picture_fill(charts, 1, 1, 32, CPRepeat, &pa_repeat, &chart_graphb_color, NULL); - - charts->finish_fill = create_picture(charts, 1, 2, 32, CPRepeat, &pa_repeat, NULL); - XRenderFillRectangle(xserver->display, PictOpSrc, charts->finish_fill, &chart_visible_color, 0, 0, 1, 1); - XRenderFillRectangle(xserver->display, PictOpSrc, charts->finish_fill, &chart_trans_color, 0, 1, 1, 1); - return charts; -_err_vmon: - vmon_destroy(&charts->vmon); - _err_charts: free(charts); @@ -343,151 +229,90 @@ void vwm_charts_destroy(vwm_charts_t *charts) } -/* copies a row from src to dest */ -static void copy_row(vwm_charts_t *charts, vwm_chart_t *chart, int src_row, Picture src, int dest_row, Picture dest) +/* moves what's below a given row up above it, preserve the lost row's graphs @ hierarchy end */ +static void snowflake_row(vwm_charts_t *charts, vwm_chart_t *chart, int row) { - XRenderComposite(charts->xserver->display, PictOpSrc, src, None, dest, - 0, src_row * CHART_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, dest_row * CHART_ROW_HEIGHT, /* dest */ - chart->width, CHART_ROW_HEIGHT); /* dimensions */ -} + VWM_TRACE("pid=%i chart=%p row=%i heirarhcy_end=%i", chart->monitor->pid, chart, row, chart->hierarchy_end); + /* stash the graph rows */ + vcr_stash_row(chart->vcr, VCR_LAYER_GRAPHA, row); + vcr_stash_row(chart->vcr, VCR_LAYER_GRAPHB, row); -/* fills a row with the specified color */ -static void fill_row(vwm_charts_t *charts, vwm_chart_t *chart, int row, Picture pic, XRenderColor *color) -{ - XRenderFillRectangle(charts->xserver->display, PictOpSrc, pic, color, - 0, row * CHART_ROW_HEIGHT, /* dest */ - chart->width, CHART_ROW_HEIGHT); /* dimensions */ -} + /* shift _all_ the layers up by 1 row */ + vcr_shift_below_row_up_one(chart->vcr, row); + /* unstash the graph rows @ hierarchy end so we have them in the snowflakes */ + vcr_unstash_row(chart->vcr, VCR_LAYER_GRAPHA, chart->hierarchy_end); + vcr_unstash_row(chart->vcr, VCR_LAYER_GRAPHB, chart->hierarchy_end); -/* copy what's below a given row up the specified amount within the same picture */ -static void shift_below_row_up(vwm_charts_t *charts, vwm_chart_t *chart, int row, Picture pic, int rows) -{ - vwm_xserver_t *xserver = charts->xserver; - - XRenderChangePicture(xserver->display, pic, CPRepeat, &pa_no_repeat); - XRenderComposite(xserver->display, PictOpSrc, pic, None, pic, - 0, (rows + row) * CHART_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, row * CHART_ROW_HEIGHT, /* dest */ - chart->width, (rows + chart->hierarchy_end) * CHART_ROW_HEIGHT - (rows + row) * CHART_ROW_HEIGHT); /* dimensions */ - XRenderChangePicture(xserver->display, pic, CPRepeat, &pa_repeat); + /* clear the others @ hierarchy end, new argv will get stamped over it */ + vcr_clear_row(chart->vcr, VCR_LAYER_TEXT, chart->hierarchy_end, -1, -1); + vcr_clear_row(chart->vcr, VCR_LAYER_SHADOW, chart->hierarchy_end, -1, -1); } -/* moves what's below a given row up above it if specified, the row becoming discarded */ -static void snowflake_row(vwm_charts_t *charts, vwm_chart_t *chart, Picture pic, int copy, int row) -{ - VWM_TRACE("pid=%i chart=%p row=%i copy=%i heirarhcy_end=%i", chart->monitor->pid, chart, row, copy, chart->hierarchy_end); - - if (copy) - copy_row(charts, chart, row, pic, 0, chart->tmp_picture); - - shift_below_row_up(charts, chart, row, pic, 1); - - if (copy) { - copy_row(charts, chart, 0, chart->tmp_picture, chart->hierarchy_end, pic); - } else { - fill_row(charts, chart, chart->hierarchy_end, pic, &chart_trans_color); - } -} - /* XXX TODO libvmon automagic children following races with explicit X client pid monitoring with different outcomes, it should be irrelevant which wins, * currently the only visible difference is the snowflakes gap (hierarchy_end) varies, which is why I haven't bothered to fix it, I barely even notice. */ -static void shift_below_row_down(vwm_charts_t *charts, vwm_chart_t *chart, int row, Picture pic, int rows) -{ - XRenderComposite(charts->xserver->display, PictOpSrc, pic, None, pic, - 0, row * CHART_ROW_HEIGHT, /* src */ - 0, 0, /* mask */ - 0, (row + rows) * CHART_ROW_HEIGHT, /* dest */ - chart->width, chart->height - (rows + row) * CHART_ROW_HEIGHT); /* dimensions */ -} - - /* shifts what's below a given row down a row, and clears the row, preparing it for populating */ -static void allocate_row(vwm_charts_t *charts, vwm_chart_t *chart, Picture pic, int row) +static void allocate_row(vwm_charts_t *charts, vwm_chart_t *chart, int row) { VWM_TRACE("pid=%i chart=%p row=%i", chart->monitor->pid, chart, row); - shift_below_row_down(charts, chart, row, pic, 1); - fill_row(charts, chart, row, pic, &chart_trans_color); + vcr_shift_below_row_down_one(chart->vcr, row); + /* FIXME TODO: the vcr layers api needs to just support bitmasks for which layers the operation + * applies to. + */ + vcr_clear_row(chart->vcr, VCR_LAYER_GRAPHA, row, -1, -1); + vcr_clear_row(chart->vcr, VCR_LAYER_GRAPHB, row, -1, -1); + vcr_clear_row(chart->vcr, VCR_LAYER_TEXT, row, -1, -1); + vcr_clear_row(chart->vcr, VCR_LAYER_SHADOW, row, -1, -1); } /* shadow a row from the text layer in the shadow layer */ static void shadow_row(vwm_charts_t *charts, vwm_chart_t *chart, int row) { - vwm_xserver_t *xserver = charts->xserver; - - /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ - XRenderComposite(xserver->display, PictOpSrc, charts->shadow_fill, chart->text_picture, chart->shadow_picture, - 0, 0, - -1, row * CHART_ROW_HEIGHT, - 0, row * CHART_ROW_HEIGHT, - chart->visible_width, CHART_ROW_HEIGHT); - - XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->text_picture, chart->shadow_picture, - 0, 0, - 0, -1 + row * CHART_ROW_HEIGHT, - 0, row * CHART_ROW_HEIGHT, - chart->visible_width, CHART_ROW_HEIGHT); - - XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->text_picture, chart->shadow_picture, - 0, 0, - 1, row * CHART_ROW_HEIGHT, - 0, row * CHART_ROW_HEIGHT, - chart->visible_width, CHART_ROW_HEIGHT); - - XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->text_picture, chart->shadow_picture, - 0, 0, - 0, 1 + row * CHART_ROW_HEIGHT, - 0, row * CHART_ROW_HEIGHT, - chart->visible_width, CHART_ROW_HEIGHT); + vcr_shadow_row(chart->vcr, VCR_LAYER_TEXT, row); } /* simple helper to map the vmon per-proc argv array into an XTextItem array, deals with threads vs. processes and the possibility of the comm field not getting read in before the process exited... */ -static void argv2xtext(const vmon_proc_t *proc, XTextItem *items, int max_items, int *nr_items) +static void proc_argv2strs(const vmon_proc_t *proc, vcr_str_t *strs, int max_strs, int *res_n_strs) { - int i; int nr = 0; + assert(proc); + assert(strs); + assert(max_strs > 2); + assert(res_n_strs); + if (proc->is_thread) { /* stick the thread marker at the start of threads */ - items[0].nchars = sizeof(CHART_ISTHREAD_ARGV) - 1; - items[0].chars = CHART_ISTHREAD_ARGV; - items[0].delta = 4; - items[0].font = None; + strs[0].str = CHART_ISTHREAD_ARGV; + strs[0].len = sizeof(CHART_ISTHREAD_ARGV) - 1; nr++; } if (((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len) { - items[nr].nchars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; + strs[nr].str = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.array; + strs[nr].len = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->comm.len - 1; } else { /* sometimes a process is so ephemeral we don't manage to sample its comm, XXX TODO: we always have a pid, stringify it? */ - items[nr].nchars = sizeof(CHART_NOCOMM_ARGV) - 1; - items[nr].chars = CHART_NOCOMM_ARGV; + strs[nr].str = CHART_NOCOMM_ARGV; + strs[nr].len = sizeof(CHART_NOCOMM_ARGV) - 1; } - items[nr].delta = 4; - items[nr].font = None; nr++; if (!proc->is_thread) { /* suppress the argv for threads */ - for (i = 1; nr < max_items && i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { - items[nr].chars = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; - items[nr].nchars = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ - items[nr].delta = 4; - items[nr].font = None; + for (int i = 1; nr < max_strs && i < ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argc; nr++, i++) { + strs[nr].str = ((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]; + strs[nr].len = strlen(((vmon_proc_stat_t *)proc->stores[VMON_STORE_PROC_STAT])->argv[i]); /* TODO: libvmon should inform us of the length */ } } - (*nr_items) = nr; + *res_n_strs = nr; } @@ -536,72 +361,37 @@ static int proc_hierarchy_changed(vmon_proc_t *proc) /* helper for drawing the vertical bars in the graph layers */ static void draw_bars(vwm_charts_t *charts, vwm_chart_t *chart, int row, double a_fraction, double a_total, double b_fraction, double b_total) { - vwm_xserver_t *xserver = charts->xserver; - int a_height, b_height; + float a_t, b_t; - /* compute the bar heights for this sample */ - a_height = (a_fraction / a_total * (double)(CHART_ROW_HEIGHT - 1)); /* give up 1 pixel for the div */ - b_height = (b_fraction / b_total * (double)(CHART_ROW_HEIGHT - 1)); + /* compute the bar %ages for this sample */ + a_t = a_fraction / a_total; + b_t = b_fraction / b_total; - /* round up to 1 pixel when the scaled result is a fraction less than 1, + /* ensure at least 1 pixel when the scaled result is a fraction less than 1, * I want to at least see 1 pixel blips for the slightest cpu utilization */ - if (a_fraction && !a_height) - a_height = 1; - - if (b_fraction && !b_height) - b_height = 1; - - /* draw the two bars for this sample at the current phase in the graphs, note the first is ceiling-based, second floor-based */ - XRenderFillRectangle(xserver->display, PictOpSrc, chart->grapha_picture, &chart_visible_color, - chart->phase, row * CHART_ROW_HEIGHT, /* dst x, y */ - 1, a_height); /* dst w, h */ - XRenderFillRectangle(xserver->display, PictOpSrc, chart->graphb_picture, &chart_visible_color, - chart->phase, row * CHART_ROW_HEIGHT + (CHART_ROW_HEIGHT - b_height) - 1, /* dst x, y */ - 1, b_height); /* dst w, h */ + vcr_draw_bar(chart->vcr, VCR_LAYER_GRAPHA, row, a_t, a_fraction > 0 ? 1 : 0); + vcr_draw_bar(chart->vcr, VCR_LAYER_GRAPHB, row, b_t, b_fraction > 0 ? 1 : 0); } /* helper for marking a finish line at the current phase for the specified row */ static void mark_finish(vwm_charts_t *charts, vwm_chart_t *chart, int row) { - vwm_xserver_t *xserver = charts->xserver; - - XRenderComposite(xserver->display, PictOpSrc, charts->finish_fill, None, chart->grapha_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - chart->phase, row * CHART_ROW_HEIGHT, /* dst x, y */ - 1, CHART_ROW_HEIGHT - 1); - XRenderComposite(xserver->display, PictOpSrc, charts->finish_fill, None, chart->graphb_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - chart->phase, row * CHART_ROW_HEIGHT, /* dst x, y */ - 1, CHART_ROW_HEIGHT - 1); + vcr_mark_finish_line(chart->vcr, VCR_LAYER_GRAPHA, row); + vcr_mark_finish_line(chart->vcr, VCR_LAYER_GRAPHB, row); } /* helper for drawing a proc's argv @ specified x offset and row on the chart */ static void print_argv(const vwm_charts_t *charts, const vwm_chart_t *chart, int x, int row, const vmon_proc_t *proc, int *res_width) { - vwm_xserver_t *xserver = charts->xserver; - XTextItem items[CHART_MAX_ARGC]; - int nr_items; - - argv2xtext(proc, items, NELEMS(items), &nr_items); - XDrawText(xserver->display, chart->text_pixmap, charts->text_gc, - x, (row + 1) * CHART_ROW_HEIGHT - 3, /* dst x, y */ - items, nr_items); - - /* if the caller wants to know the width, compute it, it's dumb that XDrawText doesn't - * return the dimensions of what was drawn, fucking xlib. - */ - if (res_width) { - int width = 0; + vcr_str_t strs[VCR_DRAW_TEXT_N_STRS_MAX]; + int n_strs; - for (int i = 0; i < nr_items; i++) - width += XTextWidth(charts->chart_font, items[i].chars, items[i].nchars) + items[i].delta; + assert(chart); - *res_width = width; - } + proc_argv2strs(proc, strs, NELEMS(strs), &n_strs); + vcr_draw_text(chart->vcr, VCR_LAYER_TEXT, x, row, strs, n_strs, res_width); } @@ -634,12 +424,10 @@ static unsigned interval_as_hz(vwm_charts_t *charts) /* draw a process' row slice of a process tree */ static void draw_tree_row(vwm_charts_t *charts, vwm_chart_t *chart, int x, int depth, int row, const vmon_proc_t *proc, int *res_width) { - vwm_xserver_t *xserver = charts->xserver; - /* only if this process isn't the root process @ the window shall we consider all relational drawing conditions */ if (proc != chart->monitor) { vmon_proc_t *child, *ancestor, *sibling, *last_sibling = NULL; - int bar_x = 0, bar_y = (row + 1) * CHART_ROW_HEIGHT; + int bar_x = 0, bar_y = (row + 1) * VCR_ROW_HEIGHT; int sub; /* XXX: everything done in this code block only dirties _this_ process' row in the rendered chart output */ @@ -647,14 +435,14 @@ static void draw_tree_row(vwm_charts_t *charts, vwm_chart_t *chart, int x, int d /* walk up the ancestors until reaching chart->monitor, any ancestors we encounter which have more siblings we draw a vertical bar for */ /* this draws the |'s in something like: | | | | comm */ for (sub = 1, ancestor = proc->parent; ancestor && ancestor != chart->monitor; ancestor = ancestor->parent, sub++) { - bar_x = ((depth - 1) - sub) * (CHART_ROW_HEIGHT / 2) + 4; + bar_x = ((depth - 1) - sub) * (VCR_ROW_HEIGHT / 2) + 4; assert(depth > 0); /* determine if the ancestor has remaining siblings which are not stale, if so, draw a connecting bar at its depth */ if (proc_has_subsequent_siblings(&charts->vmon, ancestor)) - XDrawLine(xserver->display, chart->text_pixmap, charts->text_gc, - x + bar_x, bar_y - CHART_ROW_HEIGHT, /* dst x1, y1 */ + vcr_draw_ortho_line(chart->vcr, VCR_LAYER_TEXT, + x + bar_x, bar_y - VCR_ROW_HEIGHT, /* dst x1, y1 */ x + bar_x, bar_y); /* dst x2, y2 (vertical line) */ } @@ -699,20 +487,20 @@ static void draw_tree_row(vwm_charts_t *charts, vwm_chart_t *chart, int x, int d /* found a tee is necessary, all that's left is to determine if the tee is a corner and draw it accordingly, stopping the search. */ if (needs_tee) { - bar_x = (depth - 1) * (CHART_ROW_HEIGHT / 2) + 4; + bar_x = (depth - 1) * (VCR_ROW_HEIGHT / 2) + 4; /* if we're the last sibling, corner the tee by shortening the vbar */ if (proc == last_sibling) { - XDrawLine(xserver->display, chart->text_pixmap, charts->text_gc, - x + bar_x, bar_y - CHART_ROW_HEIGHT, /* dst x1, y1 */ + vcr_draw_ortho_line(chart->vcr, VCR_LAYER_TEXT, + x + bar_x, bar_y - VCR_ROW_HEIGHT, /* dst x1, y1 */ x + bar_x, bar_y - 4); /* dst x2, y2 (vertical bar) */ } else { - XDrawLine(xserver->display, chart->text_pixmap, charts->text_gc, - x + bar_x, bar_y - CHART_ROW_HEIGHT, /* dst x1, y1 */ + vcr_draw_ortho_line(chart->vcr, VCR_LAYER_TEXT, + x + bar_x, bar_y - VCR_ROW_HEIGHT, /* dst x1, y1 */ x + bar_x, bar_y); /* dst x2, y2 (vertical bar) */ } - XDrawLine(xserver->display, chart->text_pixmap, charts->text_gc, + vcr_draw_ortho_line(chart->vcr, VCR_LAYER_TEXT, x + bar_x, bar_y - 4, /* dst x1, y1 */ x + bar_x + 2, bar_y - 4); /* dst x2, y2 (horizontal bar) */ @@ -722,7 +510,7 @@ static void draw_tree_row(vwm_charts_t *charts, vwm_chart_t *chart, int x, int d } if (res_width) - *res_width = depth * (CHART_ROW_HEIGHT / 2); + *res_width = depth * (VCR_ROW_HEIGHT / 2); } } @@ -734,7 +522,6 @@ static void draw_columns(vwm_charts_t *charts, vwm_chart_t *chart, vwm_column_t { vmon_sys_stat_t *sys_stat = charts->vmon.stores[VMON_STORE_SYS_STAT]; vmon_proc_stat_t *proc_stat = proc->stores[VMON_STORE_PROC_STAT]; - vwm_xserver_t *xserver = charts->xserver; vwm_perproc_ctxt_t *proc_ctxt = proc->foo; char str[256]; @@ -754,13 +541,9 @@ static void draw_columns(vwm_charts_t *charts, vwm_chart_t *chart, vwm_column_t * the currently configured columns, but long-term this will have to get fixed properly. */ if (c->side == VWM_SIDE_LEFT) - XRenderFillRectangle(charts->xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color, - left, row * CHART_ROW_HEIGHT, /* dst x, y */ - c->width + CHART_ROW_HEIGHT / 2, CHART_ROW_HEIGHT); /* dst w, h */ + vcr_clear_row(chart->vcr, VCR_LAYER_TEXT, row, left, c->width + VCR_ROW_HEIGHT / 2); else - XRenderFillRectangle(charts->xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color, - chart->visible_width - (c->width + CHART_ROW_HEIGHT / 2 + right), row * CHART_ROW_HEIGHT, /* dst x, y */ - c->width + CHART_ROW_HEIGHT / 2, CHART_ROW_HEIGHT); /* dst w, h */ + vcr_clear_row(chart->vcr, VCR_LAYER_TEXT, row, chart->visible_width - (c->width + VCR_ROW_HEIGHT / 2 + right), c->width + VCR_ROW_HEIGHT / 2); switch (c->type) { case VWM_COLUMN_VWM: @@ -894,9 +677,11 @@ static void draw_columns(vwm_charts_t *charts, vwm_chart_t *chart, vwm_column_t /* for plain string draws, str_len is left non-zero and they all get handled equally here */ if (str_len) { - int str_width, xpos; + const vcr_str_t strs[1] = { (vcr_str_t){.str = str, .len = str_len} }; + int str_width, xpos; - str_width = XTextWidth(charts->chart_font, str, str_len); + /* get the width first, so we can place the text, note the -1 to suppress drawings */ + vcr_draw_text(chart->vcr, VCR_LAYER_TEXT, -1 /* x */, -1 /* row */, strs, 1, &str_width); if (uniform && str_width > c->width) { c->width = str_width; chart->redraw_needed++; @@ -934,14 +719,12 @@ static void draw_columns(vwm_charts_t *charts, vwm_chart_t *chart, vwm_column_t assert(0); } - XDrawString(xserver->display, chart->text_pixmap, charts->text_gc, - xpos, (row + 1) * CHART_ROW_HEIGHT - 3, - str, str_len); + vcr_draw_text(chart->vcr, VCR_LAYER_TEXT, xpos, row, strs, 1, NULL); } if (advance) { - left += (c->side == VWM_SIDE_LEFT) * (c->width + CHART_ROW_HEIGHT / 2); - right += (c->side == VWM_SIDE_RIGHT) * (c->width + CHART_ROW_HEIGHT / 2); + left += (c->side == VWM_SIDE_LEFT) * (c->width + VCR_ROW_HEIGHT / 2); + right += (c->side == VWM_SIDE_RIGHT) * (c->width + VCR_ROW_HEIGHT / 2); } } } @@ -1014,9 +797,7 @@ static void draw_overlay_row(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc return; if (!proc->is_new) /* XXX for now always clear the row, this should be capable of being optimized in the future (if the datums driving the text haven't changed...) */ - XRenderFillRectangle(charts->xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color, - 0, row * CHART_ROW_HEIGHT, /* dst x, y */ - chart->width, CHART_ROW_HEIGHT); /* dst w, h */ + vcr_clear_row(chart->vcr, VCR_LAYER_TEXT, row, -1, -1); draw_columns(charts, chart, chart->columns, depth, row, proc); shadow_row(charts, chart, row); @@ -1073,10 +854,7 @@ static void draw_chart_rest(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_ mark_finish(charts, chart, (*row)); /* extract the row from the various layers */ - snowflake_row(charts, chart, chart->grapha_picture, 1, (*row)); - snowflake_row(charts, chart, chart->graphb_picture, 1, (*row)); - snowflake_row(charts, chart, chart->text_picture, 0, (*row)); - snowflake_row(charts, chart, chart->shadow_picture, 0, (*row)); + snowflake_row(charts, chart, (*row)); chart->snowflakes_cnt++; /* stamp the name (and whatever else we include) into chart.text_picture */ @@ -1096,10 +874,7 @@ static void draw_chart_rest(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_ /* what to do when a process has been introduced */ VWM_TRACE("%i is new", proc->pid); - allocate_row(charts, chart, chart->grapha_picture, (*row)); - allocate_row(charts, chart, chart->graphb_picture, (*row)); - allocate_row(charts, chart, chart->text_picture, (*row)); - allocate_row(charts, chart, chart->shadow_picture, (*row)); + allocate_row(charts, chart, (*row)); chart->hierarchy_end++; } @@ -1147,7 +922,6 @@ static void draw_chart_rest(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_ /* recursive draw function entrypoint, draws the IOWait/Idle/HZ row, then enters draw_chart_rest() */ static void draw_chart(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_t *proc, int *depth, int *row) { - vwm_xserver_t *xserver = charts->xserver; int prev_redraw_needed; /* CPU utilization graphs */ @@ -1156,9 +930,7 @@ static void draw_chart(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_t *pr /* only draw the \/\/\ and HZ if necessary */ if (chart->redraw_needed || charts->prev_sampling_interval != charts->sampling_interval) { - XRenderFillRectangle(xserver->display, PictOpSrc, chart->text_picture, &chart_trans_color, - 0, 0, /* dst x, y */ - chart->visible_width, CHART_ROW_HEIGHT); /* dst w, h */ + vcr_clear_row(chart->vcr, VCR_LAYER_TEXT, 0, -1, -1); draw_columns(charts, chart, chart->columns, 0, 0, proc); shadow_row(charts, chart, 0); } @@ -1188,7 +960,6 @@ static void draw_chart(vwm_charts_t *charts, vwm_chart_t *chart, vmon_proc_t *pr /* consolidated version of chart text and graph rendering, makes snowflakes integration cleaner, this always gets called regardless of the charts mode */ static void maintain_chart(vwm_charts_t *charts, vwm_chart_t *chart) { - vwm_xserver_t *xserver = charts->xserver; int row = 0, depth = 0; if (!chart->monitor || !chart->monitor->stores[VMON_STORE_PROC_STAT]) @@ -1206,10 +977,7 @@ static void maintain_chart(vwm_charts_t *charts, vwm_chart_t *chart) * For now, the monitors will just be a little latent in window resizes which is pretty harmless artifact. */ - chart->phase += (chart->width - 1); /* simply change this to .phase++ to scroll the other direction */ - chart->phase %= chart->width; - XRenderFillRectangle(xserver->display, PictOpSrc, chart->grapha_picture, &chart_trans_color, chart->phase, 0, 1, chart->height); - XRenderFillRectangle(xserver->display, PictOpSrc, chart->graphb_picture, &chart_trans_color, chart->phase, 0, 1, chart->height); + vcr_advance_phase(chart->vcr, -1); /* change this to +1 to scroll the other direction */ /* recursively draw the monitored processes to the chart */ draw_chart(charts, chart, chart->monitor, &depth, &row); @@ -1238,15 +1006,6 @@ static void proc_sample_callback(vmon_t *vmon, void *sys_cb_arg, vmon_proc_t *pr } -/* return the composed height of the chart */ -static int vwm_chart_composed_height(vwm_charts_t *charts, vwm_chart_t *chart) -{ - int snowflakes = chart->snowflakes_cnt ? 1 + chart->snowflakes_cnt : 0; /* don't include the separator row if there are no snowflakes */ - - return MIN((chart->hierarchy_end + snowflakes) * CHART_ROW_HEIGHT, chart->visible_height); -} - - /* reset snowflakes on the specified chart */ void vwm_chart_reset_snowflakes(vwm_charts_t *charts, vwm_chart_t *chart) { @@ -1257,89 +1016,15 @@ void vwm_chart_reset_snowflakes(vwm_charts_t *charts, vwm_chart_t *chart) } -static void free_chart_pictures(vwm_charts_t *charts, vwm_chart_t *chart) -{ - vwm_xserver_t *xserver = charts->xserver; - - XRenderFreePicture(xserver->display, chart->grapha_picture); - XRenderFreePicture(xserver->display, chart->graphb_picture); - XRenderFreePicture(xserver->display, chart->tmp_picture); - XRenderFreePicture(xserver->display, chart->text_picture); - XFreePixmap(xserver->display, chart->text_pixmap); - XRenderFreePicture(xserver->display, chart->shadow_picture); - XRenderFreePicture(xserver->display, chart->picture); - -} - - -static void copy_chart_pictures(vwm_charts_t *charts, vwm_chart_t *src, vwm_chart_t *dest) -{ - vwm_xserver_t *xserver = charts->xserver; - - /* XXX: note the graph pictures are copied from their current phase in the x dimension */ - XRenderComposite(xserver->display, PictOpSrc, src->grapha_picture, None, dest->grapha_picture, - src->phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - dest->phase, 0, /* dest x, y */ - src->width, src->height); - XRenderComposite(xserver->display, PictOpSrc, src->graphb_picture, None, dest->graphb_picture, - src->phase, 0, /* src x, y */ - 0, 0, /* mask x, y */ - dest->phase, 0, /* dest x, y */ - src->width, src->height); - XRenderComposite(xserver->display, PictOpSrc, src->text_picture, None, dest->text_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - src->width, src->height); - XRenderComposite(xserver->display, PictOpSrc, src->shadow_picture, None, dest->shadow_picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - src->width, src->height); - XRenderComposite(xserver->display, PictOpSrc, src->picture, None, dest->picture, - 0, 0, /* src x, y */ - 0, 0, /* mask x, y */ - 0, 0, /* dest x, y */ - src->width, src->height); -} - - /* (re)size the specified chart's visible dimensions */ int vwm_chart_set_visible_size(vwm_charts_t *charts, vwm_chart_t *chart, int width, int height) { - if (width != chart->visible_width || height != chart->visible_height) - chart->redraw_needed = 1; - - /* TODO error handling: if a create failed but we had an chart, free whatever we created and leave it be, succeed. - * if none existed it's a hard error and we must propagate it. */ - - /* if larger than the charts currently are, enlarge them */ - if (width > chart->width || height > chart->height) { - vwm_chart_t existing = *chart; - - chart->width = MAX(chart->width, MAX(width, CHART_GRAPH_MIN_WIDTH)); - chart->height = MAX(chart->height, MAX(height, CHART_GRAPH_MIN_HEIGHT)); - - chart->grapha_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, CPRepeat, &pa_repeat, &chart_trans_color, NULL); - chart->graphb_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, CPRepeat, &pa_repeat, &chart_trans_color, NULL); - chart->tmp_picture = create_picture(charts, chart->width, CHART_ROW_HEIGHT, CHART_MASK_DEPTH, 0, NULL, NULL); - - /* keep the text_pixmap reference around for XDrawText usage */ - chart->text_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, 0, NULL, &chart_trans_color, &chart->text_pixmap); - - chart->shadow_picture = create_picture_fill(charts, chart->width, chart->height, CHART_MASK_DEPTH, 0, NULL, &chart_trans_color, NULL); - chart->picture = create_picture(charts, chart->width, chart->height, 32, 0, NULL, NULL); - - if (existing.width) { - copy_chart_pictures(charts, &existing, chart); - free_chart_pictures(charts, &existing); - } - } - chart->visible_width = width; chart->visible_height = height; + if (vcr_resize_visible(chart->vcr, width, height) > 0) + chart->redraw_needed = 1; + return 1; } @@ -1394,6 +1079,8 @@ vwm_chart_t * vwm_chart_create(vwm_charts_t *charts, int pid, int width, int hei chart->hierarchy_end = 1 + count_rows(chart->monitor); chart->gen_last_composed = -1; + chart->vcr = vcr_new(charts->vcr_backend, &chart->hierarchy_end, &chart->snowflakes_cnt); + if (!vwm_chart_set_visible_size(charts, chart, width, height)) { VWM_ERROR("Unable to set initial chart size"); goto _err_unmonitor; @@ -1416,19 +1103,17 @@ _err: void vwm_chart_destroy(vwm_charts_t *charts, vwm_chart_t *chart) { vmon_proc_unmonitor(&charts->vmon, chart->monitor, (void (*)(vmon_t *, void *, vmon_proc_t *, void *))proc_sample_callback, chart); - free_chart_pictures(charts, chart); + vcr_free(chart->vcr); + free(chart->name); free(chart); } /* this composes the maintained chart into the base chart picture, this gets called from paint_all() on every repaint of xwin */ /* we noop the call if the gen_last_composed and monitor->proc.generation numbers match, indicating there's nothing new to compose. */ -void vwm_chart_compose(vwm_charts_t *charts, vwm_chart_t *chart, XserverRegion *res_damaged_region) +void vwm_chart_compose(vwm_charts_t *charts, vwm_chart_t *chart) { - vwm_xserver_t *xserver = charts->xserver; - int height; - - if (!chart->width || !chart->height) + if (!chart->visible_width || !chart->visible_height) return; if (chart->gen_last_composed == chart->monitor->generation) @@ -1438,114 +1123,31 @@ void vwm_chart_compose(vwm_charts_t *charts, vwm_chart_t *chart, XserverRegion * //VWM_TRACE("composing %p", chart); - height = vwm_chart_composed_height(charts, chart); - - /* fill the chart picture with the background */ - XRenderComposite(xserver->display, PictOpSrc, charts->bg_fill, None, chart->picture, - 0, 0, - 0, 0, - 0, 0, - chart->visible_width, height); - - /* draw the graphs into the chart through the stencils being maintained by the sample callbacks */ - XRenderComposite(xserver->display, PictOpOver, charts->grapha_fill, chart->grapha_picture, chart->picture, - 0, 0, - chart->phase, 0, - 0, 0, - chart->visible_width, height); - XRenderComposite(xserver->display, PictOpOver, charts->graphb_fill, chart->graphb_picture, chart->picture, - 0, 0, - chart->phase, 0, - 0, 0, - chart->visible_width, height); - - /* draw the shadow into the chart picture using a translucent black source drawn through the shadow mask */ - XRenderComposite(xserver->display, PictOpOver, charts->shadow_fill, chart->shadow_picture, chart->picture, - 0, 0, - 0, 0, - 0, 0, - chart->visible_width, height); - - /* render chart text into the chart picture using a white source drawn through the chart text as a mask, on top of everything */ - XRenderComposite(xserver->display, PictOpOver, charts->text_fill, chart->text_picture, chart->picture, - 0, 0, - 0, 0, - 0, 0, - chart->visible_width, (chart->hierarchy_end * CHART_ROW_HEIGHT)); - - XRenderComposite(xserver->display, PictOpOver, charts->snowflakes_text_fill, chart->text_picture, chart->picture, - 0, 0, - 0, chart->hierarchy_end * CHART_ROW_HEIGHT, - 0, chart->hierarchy_end * CHART_ROW_HEIGHT, - chart->visible_width, height - (chart->hierarchy_end * CHART_ROW_HEIGHT)); - - /* damage the window to ensure the updated chart is drawn (TODO: this can be done more selectively/efficiently) */ - if (res_damaged_region) { - XRectangle damage = {}; - - damage.width = chart->visible_width; - damage.height = chart->visible_height; - - *res_damaged_region = XFixesCreateRegion(xserver->display, &damage, 1); - } + /* FIXME TODO: errors */ (void) vcr_compose(chart->vcr); } -/* render the chart into a picture at the specified coordinates and dimensions */ -void vwm_chart_render(vwm_charts_t *charts, vwm_chart_t *chart, int op, Picture dest, int x, int y, int width, int height) +#ifdef USE_XLIB +/* xdamage producing variant of the above for vwm composited WM use */ +void vwm_chart_compose_xdamage(vwm_charts_t *charts, vwm_chart_t *chart, XserverRegion *res_damaged_region) { - vwm_xserver_t *xserver = charts->xserver; - - if (!chart->width || !chart->height) - return; - - /* draw the monitoring chart atop dest, note we stay within the window borders here. */ - XRenderComposite(xserver->display, op, chart->picture, None, dest, - 0, 0, 0, 0, /* src x,y, maxk x, y */ - x, /* dst x */ - y, /* dst y */ - width, MIN(vwm_chart_composed_height(charts, chart), height) /* FIXME */); /* w, h */ -} - - -/* render the chart into a newly allocated pixmap, intended for snapshotting purposes */ -void vwm_chart_render_as_pixmap(vwm_charts_t *charts, vwm_chart_t *chart, const XRenderColor *bg_color, Pixmap *res_pixmap) -{ - static const XRenderColor blackness = { 0x0000, 0x0000, 0x0000, 0xFFFF}; - Picture dest; - assert(charts); assert(chart); - assert(res_pixmap); + assert(res_damaged_region); - if (!bg_color) - bg_color = &blackness; - - dest = create_picture_fill(charts, chart->width, vwm_chart_composed_height(charts, chart), 32, 0, NULL, bg_color, res_pixmap); - vwm_chart_render(charts, chart, PictOpOver, dest, 0, 0, chart->width, vwm_chart_composed_height(charts, chart)); - XRenderFreePicture(charts->xserver->display, dest); + vwm_chart_compose(charts, chart); + /* damage the window to ensure the updated chart is drawn (TODO: this can be done more selectively/efficiently) */ + /* TODO errors: */ (void) vcr_get_composed_xdamage(chart->vcr, res_damaged_region); } +#endif - -void vwm_chart_render_as_ximage(vwm_charts_t *charts, vwm_chart_t *chart, const XRenderColor *bg_color, XImage **res_ximage) +/* render the chart into a picture at the specified coordinates and dimensions */ +void vwm_chart_render(vwm_charts_t *charts, vwm_chart_t *chart, vcr_present_op_t op, vcr_dest_t *dest, int x, int y, int width, int height) { - Pixmap dest_pixmap; + if (!chart->visible_width || !chart->visible_height) + return; - assert(charts); - assert(chart); - assert(res_ximage); - - vwm_chart_render_as_pixmap(charts, chart, bg_color, &dest_pixmap); - *res_ximage = XGetImage(charts->xserver->display, - dest_pixmap, - 0, - 0, - chart->width, - vwm_chart_composed_height(charts, chart), - AllPlanes, - ZPixmap); - - XFreePixmap(charts->xserver->display, dest_pixmap); + vcr_present(chart->vcr, op, dest, x, y, width, height); } diff --git a/src/charts.h b/src/charts.h index 9e28afa..4737267 100644 --- a/src/charts.h +++ b/src/charts.h @@ -1,15 +1,17 @@ #ifndef _CHARTS_H #define _CHARTS_H -#include <X11/extensions/Xfixes.h> -#include <X11/extensions/Xrender.h> - +#ifdef USE_XLIB +#include <X11/extensions/Xfixes.h> /* this is just for XserverRegion/vwm_chart_compose_xdamage() */ #include "xserver.h" +#endif + +#include "vcr.h" typedef struct _vwm_charts_t vwm_charts_t; typedef struct _vwm_chart_t vwm_chart_t; -vwm_charts_t * vwm_charts_create(vwm_xserver_t *xserver); +vwm_charts_t * vwm_charts_create(vcr_backend_t *vbe); void vwm_charts_destroy(vwm_charts_t *charts); void vwm_charts_rate_increase(vwm_charts_t *charts); void vwm_charts_rate_decrease(vwm_charts_t *charts); @@ -20,9 +22,15 @@ vwm_chart_t * vwm_chart_create(vwm_charts_t *charts, int pid, int width, int hei void vwm_chart_destroy(vwm_charts_t *charts, vwm_chart_t *chart); void vwm_chart_reset_snowflakes(vwm_charts_t *charts, vwm_chart_t *chart); int vwm_chart_set_visible_size(vwm_charts_t *charts, vwm_chart_t *chart, int width, int height); -void vwm_chart_compose(vwm_charts_t *charts, vwm_chart_t *chart, XserverRegion *res_damaged_region); -void vwm_chart_render(vwm_charts_t *charts, vwm_chart_t *chart, int op, Picture dest, int x, int y, int width, int height); -void vwm_chart_render_as_pixmap(vwm_charts_t *charts, vwm_chart_t *chart, const XRenderColor *bg_color, Pixmap *res_pixmap); -void vwm_chart_render_as_ximage(vwm_charts_t *charts, vwm_chart_t *chart, const XRenderColor *bg_color, XImage **res_ximage); +void vwm_chart_compose(vwm_charts_t *charts, vwm_chart_t *chart); +#ifdef USE_XLIB +/* XXX: this is annoying, and frankly could probably go away if I don't ever actually bother with producing + * an accurate damaged region. Right now it's just a visible area of the composed charts rectangle, + * which could just as well be served by a simple x,y,w,h visible area description the caller could then + * turn into an XserverRegion is desired. + */ +void vwm_chart_compose_xdamage(vwm_charts_t *charts, vwm_chart_t *chart, XserverRegion *res_damaged_region); +#endif +void vwm_chart_render(vwm_charts_t *charts, vwm_chart_t *chart, vcr_present_op_t op, vcr_dest_t *dest, int x, int y, int width, int height); #endif diff --git a/src/composite.c b/src/composite.c index f25d7c6..353253f 100644 --- a/src/composite.c +++ b/src/composite.c @@ -27,6 +27,7 @@ #include "charts.h" #include "xwindow.h" +#include "vcr.h" #include "vwm.h" /* compositing manager stuff */ @@ -38,6 +39,7 @@ typedef enum _vwm_compositing_mode_t { static vwm_compositing_mode_t compositing_mode = VWM_COMPOSITING_OFF; /* current compositing mode */ static XserverRegion combined_damage = None; static Picture root_picture = None, root_buffer = None; /* compositing gets double buffered */ +static vcr_dest_t *root_dest; static XWindowAttributes root_attrs; static XRenderPictureAttributes pa_inferiors = { .subwindow_mode = IncludeInferiors }; static int repaint_needed; @@ -172,6 +174,7 @@ void vwm_composite_invalidate_root(vwm_t *vwm) if (root_buffer) { XRenderFreePicture(VWM_XDISPLAY(vwm), root_buffer); root_buffer = None; + root_dest = vcr_dest_free(root_dest); } } @@ -211,6 +214,7 @@ void vwm_composite_paint_all(vwm_t *vwm) CPSubwindowMode, &pa_inferiors); root_pixmap = XCreatePixmap(VWM_XDISPLAY(vwm), VWM_XROOT(vwm), root_attrs.width, root_attrs.height, VWM_XDEPTH(vwm)); root_buffer = XRenderCreatePicture(VWM_XDISPLAY(vwm), root_pixmap, XRenderFindVisualFormat(VWM_XDISPLAY(vwm), VWM_XVISUAL(vwm)), 0, 0); + root_dest = vcr_dest_xpicture_new(vwm->vcr_backend, root_buffer); XFreePixmap(VWM_XDISPLAY(vwm), root_pixmap); } @@ -245,7 +249,7 @@ void vwm_composite_paint_all(vwm_t *vwm) if (xwin->chart) { XserverRegion chart_damage = None; - vwm_chart_compose(vwm->charts, xwin->chart, &chart_damage); + vwm_chart_compose_xdamage(vwm->charts, xwin->chart, &chart_damage); if (chart_damage != None) { /* the damage region is in chart coordinate space, translation necessary. */ XFixesTranslateRegion(VWM_XDISPLAY(vwm), chart_damage, @@ -288,7 +292,7 @@ void vwm_composite_paint_all(vwm_t *vwm) if (xwin->chart) { /* draw the monitoring chart atop the window, note we stay within the window borders here. */ - vwm_chart_render(vwm->charts, xwin->chart, PictOpOver, root_buffer, + vwm_chart_render(vwm->charts, xwin->chart, VCR_PRESENT_OP_OVER, root_dest, xwin->attrs.x + xwin->attrs.border_width, xwin->attrs.y + xwin->attrs.border_width, xwin->attrs.width, diff --git a/src/vcr.c b/src/vcr.c new file mode 100644 index 0000000..41b4f0d --- /dev/null +++ b/src/vcr.c @@ -0,0 +1,2176 @@ +/* + * \/\/\ + * + * Copyright (C) 2024 Vito Caputo - <vcaputo@pengaru.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * 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/>. + */ + +/* vcr = vwm charts rendering (api) + * + * This exists to decouple the rendering needs of charts.c/vmon.c from Xlib for + * enabling headless use of vmon in embedded/server circumstances. + * + * Rather than exploring serverless Xlib implementations which render + * in-process, embracing something like cairo, or worse - creating a new + * generic rendering library, a very targeted approach has been taken here + * catering specifically to the requirements of charts.c. + * + * The intention is to enable higher density in-memory representation of the + * chart layers especially relevant to embedded situations. The existing Xlib + * Render based charts usage utilizes several full-color full-size Picture + * objects for the various layers used to compose the charts. + * + * The vcr api should implement layers/planes for a common underlying object as + * a first-class entity. When the vcr object is X-backed, those layers may be + * Pictures just like previously. But when the vcr object is headless, those + * layers are free to be represented in whatever packed format makes the most + * sense, without consideration for real-time rendering efficiency. + * + * Imagine for instance if headless vcr allocated a single byte array + * dimensioned by the chart's dimensions, to represent eight layers in the bit + * planes of the bytes. This exploits the fact that chart layers contain + * monochromatic coverage information for the pixel. Turning these layers into + * color renderings could use a simple palette lookup to produce the + * appropriate blended colors given the combination of bits set in the various + * layers. + * + * So every layer would have a color associated with it, used when compositing + * the rendered chart from the layers. But the actual layer maintenance would + * simply be setting/unsetting the appropriate bit in the affected pixel + * positions. + */ + +#include <assert.h> +#include <math.h> +#include <poll.h> +#include <stdarg.h> +#include <stdint.h> +#include <stdlib.h> + +#ifdef USE_XLIB +#include <X11/Xlib.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xrender.h> +#include "xserver.h" +#endif /* USE_XLIB */ + +#ifdef USE_PNG +#include <png.h> +#endif /* USE_PNG */ + +#include "ascii.h" +#include "util.h" +#include "vcr.h" + +/* backend is the root vcr object everything else here derives from, + * for an X backend it encompasses the xserver/display connection. + */ +typedef struct vcr_backend_t { + vcr_backend_type_t type; + + union { +#ifdef USE_XLIB + struct { + vwm_xserver_t *xserver; + + unsigned xserver_created:1; + Atom wm_protocols_atom; + Atom wm_delete_atom; + + /* X stuff needed for doing all the vwm_charts_t things + * (these once resided in vwm_charts_t) + */ + XFontStruct *chart_font; + GC text_gc; + Picture shadow_fill, + text_fill, + bg_fill, + snowflakes_text_fill, + grapha_fill, + graphb_fill, + finish_fill; + } xlib; +#endif /* USE_XLIB */ + struct { + /* TODO */ + } mem; + }; +} vcr_backend_t; + + +/* vcr is the per-chart object you can present to a dest object, + * it is tightly bound to backend type and shares the vcr_backend_type_t. + * + * where the backend encompasses a bunch of backend-global state applicable to all + * charts like the GC / "fill" picture sources etc, this object encompasses the chart-specific + * state like graph layer/shadow/text pictures etc. + */ +typedef struct vcr_t { + vcr_backend_t *backend; + + int width; /* current width of the chart */ + int height; /* current height of the chart */ + int visible_width; /* currently visible width of the chart */ + int visible_height; /* currently visible height of the chart */ + int phase; /* current position within the (horizontally scrolling) graphs */ + + /* these pointers point into variables within the chart_t because they're primarily maintained by + * the chart renderer, but we need to access them occasionally here.. it's a bit gross. + */ + int *hierarchy_end_ptr; /* pointer to row where the process hierarchy currently ends */ + int *snowflakes_cnt_ptr; /* pointer to count of snowflaked rows (reset to zero to truncate snowflakes display) */ + + union { +#ifdef USE_XLIB + struct { + Pixmap text_pixmap; /* pixmap for charted text (kept around for XDrawText usage) */ + Picture text_picture; /* picture representation of text_pixmap */ + Picture shadow_picture; /* text shadow layer */ + Picture grapha_picture; /* graph A layer */ + Picture graphb_picture; /* graph B layer */ + Picture tmp_a_picture; /* 1 row worth of temporary graph A space */ + Picture tmp_b_picture; /* 1 row worth of temporary graph B space */ + Picture picture; /* chart picture derived from the pixmap, for render compositing */ + } xlib; +#endif /* USE_XLIB */ + struct { + uint8_t *bits; /* width * height bytes are used to represent the coverage status of up to 8 layers */ + uint8_t *tmp; /* width * VCR_ROW_HEIGHT bytes for a row's worth of temporary storage */ + } mem; + }; +} vcr_t; + + +/* dest represents an output destination for rendering/compositing vcr instances at. + * for an vmon-on-X scenario, it encompasses the viewable X Window + Picture of of vmon. + * for an headless vmon-to-PNGs scenario, it encompasses the PNG writer. + * for an vwm-on-X scenario, it encompasses the Picture associated with a vwm_window_t. + * + * it should also be possible to do things like render a vcr instance created from an xlib backend + * to something like a PNG dest. + */ +typedef enum vcr_dest_type_t { +#ifdef USE_XLIB + VCR_DEST_TYPE_XWINDOW, + VCR_DEST_TYPE_XPICTURE, +#endif /* USE_XLIB */ + VCR_DEST_TYPE_PNG +} vcr_dest_type_t; + + +typedef struct vcr_dest_t { + vcr_backend_t *backend; + vcr_dest_type_t type; + + union { +#ifdef USE_XLIB + struct { + /* vmon use case; xwindow dest maps to vmon's X window, no compositing */ + Window window; + Picture picture; + } xwindow; + + struct { + /* vwm use case; xpicture dest maps to composited X root window */ + Picture picture; + } xpicture; +#endif /* USE_XLIB */ +#ifdef USE_PNG + struct { + /* vmon use case; png dest is for persisting snapshots of the vcr state */ + /* this could actually apply to vwm too which would be useful for hot-key based + * snapshotting of a focused window's monitoring overlays. + */ + png_infop info_ctx; + png_structp png_ctx; + FILE *output; + } png; +#endif /* USE_PNG */ + }; +} vcr_dest_t; + + +#ifdef USE_XLIB +#define CHART_GRAPH_MIN_WIDTH 200 /* always create graphs at least this large */ +#define CHART_GRAPH_MIN_HEIGHT (4 * VCR_ROW_HEIGHT) +#define CHART_MASK_DEPTH 8 /* XXX: 1 would save memory, but Xorg isn't good at it */ +#define CHART_FIXED_FONT "-misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso10646-1" + +static XRenderColor chart_visible_color = { 0xffff, 0xffff, 0xffff, 0xffff }, + chart_shadow_color = { 0x0000, 0x0000, 0x0000, 0xC000}, + chart_bg_color = { 0x0, 0x1000, 0x0, 0x9000}, + chart_div_color = { 0x2000, 0x3000, 0x2000, 0x9000}, + chart_snowflakes_visible_color = { 0xd000, 0xd000, 0xd000, 0x8000 }, + chart_trans_color = {0x00, 0x00, 0x00, 0x00}, + chart_grapha_color = { 0xff00, 0x0000, 0x0000, 0x3000 }, /* ~red */ + chart_graphb_color = { 0x0000, 0xffff, 0xffff, 0x3000 }; /* ~cyan */ + +static XRenderPictureAttributes pa_repeat = { .repeat = 1 }; +static XRenderPictureAttributes pa_no_repeat = { .repeat = 0 }; + + +/* convenience helper for creating a pixmap */ +static Pixmap create_pixmap(vwm_xserver_t *xserver, unsigned width, unsigned height, unsigned depth) +{ + return XCreatePixmap(xserver->display, XSERVER_XROOT(xserver), width, height, depth); +} + + +/* convenience helper for creating a picture, supply res_pixmap to keep a reference to the pixmap drawable. */ +static Picture create_picture(vwm_xserver_t *xserver, unsigned width, unsigned height, unsigned depth, unsigned long attr_mask, XRenderPictureAttributes *attr, Pixmap *res_pixmap) +{ + Pixmap pixmap; + Picture picture; + int format; + + /* FIXME this pixmap->picture dance seems silly, investigate further. TODO */ + switch (depth) { + case 8: + format = PictStandardA8; + break; + case 32: + format = PictStandardARGB32; + break; + default: + assert(0); + } + + pixmap = create_pixmap(xserver, width, height, depth); + picture = XRenderCreatePicture(xserver->display, pixmap, XRenderFindStandardFormat(xserver->display, format), attr_mask, attr); + + if (res_pixmap) { + *res_pixmap = pixmap; + } else { + XFreePixmap(xserver->display, pixmap); + } + + return picture; +} + + +/* convenience helper for creating a filled picture, supply res_pixmap to keep a reference to the pixmap drawable. */ +static Picture create_picture_fill(vwm_xserver_t *xserver, unsigned width, unsigned height, unsigned depth, unsigned long attrs_mask, XRenderPictureAttributes *attrs, const XRenderColor *color, Pixmap *res_pixmap) +{ + Picture picture; + + picture = create_picture(xserver, width, height, depth, attrs_mask, attrs, res_pixmap); + XRenderFillRectangle(xserver->display, PictOpSrc, picture, color, 0, 0, width, height); + + return picture; +} + + +/* returns NULL on failure, freeing vcr + * returns vcr on success, fully setup for the given backend. + */ +static vcr_backend_t * vcr_backend_xlib_setup(vcr_backend_t *vbe, vwm_xserver_t *xserver) +{ + Pixmap bitmask; + + assert(vbe); + + vbe->type = VCR_BACKEND_TYPE_XLIB; + + if (!xserver) { + /* we'll connect to the xserver if none is provided */ + xserver = vwm_xserver_open(); + if (!xserver) { + VWM_ERROR("unable to open xserver"); + goto err_vbe; + } + vbe->xlib.xserver_created = 1; + } + vbe->xlib.xserver = xserver; + + /* this is really only needed for the xwindow-dest/vmon scenario (where we create the xserver), + * but let's just always grab the atoms anyways + */ + vbe->xlib.wm_delete_atom = XInternAtom(xserver->display, "WM_DELETE_WINDOW", False); + vbe->xlib.wm_protocols_atom = XInternAtom(xserver->display, "WM_PROTOCOLS", False); + + /* get all the text and graphics stuff setup for charts, + * this all used to be part of vwm_charts_create(), but + * moved here as charts.c/vmon.c became X-decoupled and + * relied on vcr.c to abstract the X/mem/png specifics + * on the road to headless vmon support. + */ + vbe->xlib.chart_font = XLoadQueryFont(xserver->display, CHART_FIXED_FONT); + if (!vbe->xlib.chart_font) { + VWM_ERROR("unable to load chart font \"%s\"", CHART_FIXED_FONT); + goto err_vbe; + } + + /* FIXME: error handling for all this junk */ + /* create a GC for rendering the text using Xlib into the text chart stencils */ + bitmask = create_pixmap(xserver, 1, 1, CHART_MASK_DEPTH); + vbe->xlib.text_gc = XCreateGC(xserver->display, bitmask, 0, NULL); + XSetForeground(xserver->display, vbe->xlib.text_gc, WhitePixel(xserver->display, xserver->screen_num)); + XFreePixmap(xserver->display, bitmask); + + /* create some repeating source fill pictures for drawing through the text and graph stencils */ + vbe->xlib.text_fill = create_picture_fill(xserver, 1, 1, 32, CPRepeat, &pa_repeat, &chart_visible_color, NULL); + vbe->xlib.shadow_fill = create_picture_fill(xserver, 1, 1, 32, CPRepeat, &pa_repeat, &chart_shadow_color, NULL); + + vbe->xlib.bg_fill = create_picture(xserver, 1, VCR_ROW_HEIGHT, 32, CPRepeat, &pa_repeat, NULL); + XRenderFillRectangle(xserver->display, PictOpSrc, vbe->xlib.bg_fill, &chart_bg_color, 0, 0, 1, VCR_ROW_HEIGHT); + XRenderFillRectangle(xserver->display, PictOpSrc, vbe->xlib.bg_fill, &chart_div_color, 0, VCR_ROW_HEIGHT - 1, 1, 1); + + vbe->xlib.snowflakes_text_fill = create_picture_fill(xserver, 1, 1, 32, CPRepeat, &pa_repeat, &chart_snowflakes_visible_color, NULL); + vbe->xlib.grapha_fill = create_picture_fill(xserver, 1, 1, 32, CPRepeat, &pa_repeat, &chart_grapha_color, NULL); + vbe->xlib.graphb_fill = create_picture_fill(xserver, 1, 1, 32, CPRepeat, &pa_repeat, &chart_graphb_color, NULL); + + vbe->xlib.finish_fill = create_picture(xserver, 1, 2, 32, CPRepeat, &pa_repeat, NULL); + XRenderFillRectangle(xserver->display, PictOpSrc, vbe->xlib.finish_fill, &chart_visible_color, 0, 0, 1, 1); + XRenderFillRectangle(xserver->display, PictOpSrc, vbe->xlib.finish_fill, &chart_trans_color, 0, 1, 1, 1); + + return vbe; + +err_vbe: + if (vbe->xlib.xserver_created) + vwm_xserver_close(vbe->xlib.xserver); + + free(vbe); + + return NULL; +} +#endif /* USE_XLIB */ + + +vcr_backend_t * vcr_backend_new(vcr_backend_type_t backend, ...) +{ + vcr_backend_t *vbe; + va_list ap; + + vbe = calloc(1, sizeof(vcr_backend_t)); + if (!vbe) + return NULL; + + va_start(ap, backend); + switch (backend) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: + vbe = vcr_backend_xlib_setup(vbe, va_arg(ap, vwm_xserver_t *)); + break; +#endif /* USE_XLIB */ + case VCR_BACKEND_TYPE_MEM: + vbe->type = VCR_BACKEND_TYPE_MEM; + break; + default: + assert(0); + } + va_end(ap); + + return vbe; +} + + +/* returns the native dimensions of the backend, really only applies to xlib currently for fullscreen dims */ +int vcr_backend_get_dimensions(vcr_backend_t *vbe, int *res_width, int *res_height) +{ + assert(vbe); + assert(res_width); + assert(res_height); + + switch (vbe->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + XWindowAttributes wattr; + + if (!XGetWindowAttributes(vbe->xlib.xserver->display, XSERVER_XROOT(vbe->xlib.xserver), &wattr)) { + return -ENOENT; + } + + *res_width = wattr.width; + *res_height = wattr.height; + + return 0; + } +#endif + + case VCR_BACKEND_TYPE_MEM: + return -ENOTSUP; + + default: + assert(0); + } +} + + +/* this is basically just needed by the vmon use case */ +/* returns 1 if the backend has events to process, 0 on timeout/error. */ +int vcr_backend_poll(vcr_backend_t *vbe, int timeout) +{ + /* FIXME TODO: should probably switch to ppoll() and keep signals blocked outside of + * while blocked in ppoll(). Then ppoll() becomes our signal delivery/handling point... + */ + switch (vbe->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + struct pollfd pfd = { + .events = POLLIN, + .fd = ConnectionNumber(vbe->xlib.xserver->display), + }; + + if (XPending(vbe->xlib.xserver->display)) + return 1; + + return poll(&pfd, 1, timeout); + } +#endif + case VCR_BACKEND_TYPE_MEM: + return poll(NULL, 0, timeout); + + default: + assert(0); + } +} + + +/* this is basically just needed by the vmon use case, called after + * vcr_backend_poll() returns 1. + * if VCR_BACKEND_EVENT_RESIZE is returned, res_width and res_height will be updated. + */ +vcr_backend_event_t vcr_backend_next_event(vcr_backend_t *vbe, int *res_width, int *res_height) +{ + assert(vbe); + assert(res_width); + assert(res_height); + + switch (vbe->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + XEvent ev; + + XNextEvent(vbe->xlib.xserver->display, &ev); + + switch (ev.type) { + case ConfigureNotify: + *res_width = ev.xconfigure.width; + *res_height = ev.xconfigure.height; + return VCR_BACKEND_EVENT_RESIZE; + + case Expose: + return VCR_BACKEND_EVENT_REDRAW; + + case ClientMessage: + if (ev.xclient.message_type != vbe->xlib.wm_protocols_atom) + break; + + if (ev.xclient.data.l[0] != vbe->xlib.wm_delete_atom) + break; + + return VCR_BACKEND_EVENT_QUIT; + } + break; + } +#endif + + case VCR_BACKEND_TYPE_MEM: + break; + + default: + assert(0); + } + + return VCR_BACKEND_EVENT_NOOP; +} + + +vcr_backend_t * vcr_backend_free(vcr_backend_t *vbe) +{ + if (vbe) { + switch (vbe->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.shadow_fill); + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.text_fill); + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.bg_fill); + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.snowflakes_text_fill); + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.grapha_fill); + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.graphb_fill); + XRenderFreePicture(vbe->xlib.xserver->display, vbe->xlib.finish_fill); + XFreeFont(vbe->xlib.xserver->display, vbe->xlib.chart_font); + XFreeGC(vbe->xlib.xserver->display, vbe->xlib.text_gc); + + if (vbe->xlib.xserver_created) + vwm_xserver_close(vbe->xlib.xserver); + break; +#endif /* USE_XLIB */ + case VCR_BACKEND_TYPE_MEM: + break; + default: + assert(0); + } + + free(vbe); + } + + return NULL; +} + + +#ifdef USE_XLIB +/* for the vmon use case, we need a window destination created */ +vcr_dest_t * vcr_dest_xwindow_new(vcr_backend_t *vbe, const char *name, unsigned width, unsigned height) +{ + XWindowAttributes wattr = {}; + XRenderPictureAttributes pattr = {}; + vwm_xserver_t *xserver; + vcr_dest_t *dest; + + assert(vbe); + assert(vbe->type == VCR_BACKEND_TYPE_XLIB); + assert(width > 0); + assert(height > 0); + + xserver = vbe->xlib.xserver; + + dest = calloc(1, sizeof(vcr_dest_t)); + if (!dest) + return NULL; + + dest->type = VCR_DEST_TYPE_XWINDOW; + dest->backend = vbe; + + dest->xwindow.window = XCreateSimpleWindow(xserver->display, XSERVER_XROOT(xserver), 0, 0, width, height, 1, 0, 0); + if (name) + XStoreName(xserver->display, dest->xwindow.window, name); + XGetWindowAttributes(xserver->display, dest->xwindow.window, &wattr); + dest->xwindow.picture = XRenderCreatePicture(xserver->display, dest->xwindow.window, XRenderFindVisualFormat(xserver->display, wattr.visual), 0, &pattr); + XMapWindow(xserver->display, dest->xwindow.window); + XSelectInput(xserver->display, dest->xwindow.window, StructureNotifyMask|ExposureMask); + XSync(xserver->display, False); + + return dest; +} + + +/* accessor to get the Window id out of the dest */ +unsigned vcr_dest_xwindow_get_id(vcr_dest_t *dest) +{ + assert(dest); + assert(dest->type == VCR_DEST_TYPE_XWINDOW); + + return dest->xwindow.window; +} + + +vcr_dest_t * vcr_dest_xpicture_new(vcr_backend_t *vbe, Picture picture) +{ + vcr_dest_t *dest; + + assert(vbe); + assert(vbe->type == VCR_BACKEND_TYPE_XLIB); + assert(picture != None); + + dest = calloc(1, sizeof(vcr_dest_t)); + if (!dest) + return NULL; + + dest->type = VCR_DEST_TYPE_XPICTURE; + dest->backend = vbe; + dest->xpicture.picture = picture; + + return dest; +} +#endif /* USE_XLIB */ + + +#ifdef USE_PNG +vcr_dest_t * vcr_dest_png_new(vcr_backend_t *vbe, FILE *output) +{ + vcr_dest_t *dest; + + assert(vbe); + assert(output != NULL); + + /* for png dest we just have to make sure vbe->type is one + * we can handle presenting from... the png dest doesn't + * actually have to derive any resources from the backend, + * unlike the xwindow dest which actually has to create a + * window etc. + */ + + dest = calloc(1, sizeof(vcr_dest_t)); + if (!dest) + return NULL; + + dest->type = VCR_DEST_TYPE_PNG; + dest->png.output = output; + + dest->png.png_ctx = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!dest->png.png_ctx) { + free(dest); + return NULL; + } + + dest->png.info_ctx = png_create_info_struct(dest->png.png_ctx); + if (!dest->png.info_ctx) { + png_destroy_write_struct(&dest->png.png_ctx, NULL); + free(dest); + return NULL; + } + + png_init_io(dest->png.png_ctx, output); + + return dest; +} +#endif /* USE_PNG */ + + +vcr_dest_t * vcr_dest_free(vcr_dest_t *dest) +{ + if (dest) { + switch (dest->type) { +#ifdef USE_XLIB + case VCR_DEST_TYPE_XWINDOW: + XDestroyWindow(dest->backend->xlib.xserver->display, dest->xwindow.window); + XRenderFreePicture(dest->backend->xlib.xserver->display, dest->xwindow.picture); + break; + case VCR_DEST_TYPE_XPICTURE: + XRenderFreePicture(dest->backend->xlib.xserver->display, dest->xpicture.picture); + break; +#endif /* USE_XLIB */ +#ifdef USE_PNG + case VCR_DEST_TYPE_PNG: + /* XXX: we don't take ownership of the FILE* @ dest->png.output, but + * that could change. The thinking being the caller provided the + * FILE* pre-opened, and it could very well be something like stdout, + * which we might not want to close immediately after writing the png + * to it. But maybe it'd be better to just take ownership of it. + */ + png_destroy_write_struct(&dest->png.png_ctx, &dest->png.info_ctx); + break; +#endif /* USE_PNG */ + default: + assert(0); + } + + free(dest); + } + + return NULL; +} + + +/* vcr is the workhorse of doing the actual chart compositing/rendering using a given backend. + * + * The vcr object encapsulates a chart instance and the state of all its layers, in whatever form + * is appropriate for the backend it's derived from. + * + * In the xlib backend case, that closely resembles what the OG charts.c X-coupled implementation + * did, just shoved behind the vcr api. This results in efficient vcr_present() to X dests for + * real-time usage. + * + * In the mem backend case, X types are not used at all, and an ad-hoc packed byte array is used to + * represent the various chart layers as bit planes in the interests of saving memory. This makes for + * slower manipulation and compositing, and a slower present, but is intended for more embedded headless + * uses where the priority is more lower frequency (1HZ) and more history (larger dimensions) with periodic + * PNG presents on the order of minutes/hours for cloud uploading to facilitate investigations. + */ +vcr_t * vcr_new(vcr_backend_t *vbe, int *hierarchy_end_ptr, int *snowflakes_cnt_ptr) +{ + vcr_t *vcr; + + assert(vbe); + assert(hierarchy_end_ptr); + assert(snowflakes_cnt_ptr); + + vcr = calloc(1, sizeof(vcr_t)); + if (!vcr) + return NULL; + + vcr->backend = vbe; + vcr->hierarchy_end_ptr = hierarchy_end_ptr; + vcr->snowflakes_cnt_ptr = snowflakes_cnt_ptr; + + return vcr; +} + + +#ifdef USE_XLIB +/* helper for _only_ freeing the xlib internal stuff embedded within the vcr, + * split out because resizes need to throw this stuff away after copying to the + * newly allocated instances. + */ +static void vcr_free_xlib_internal(vcr_t *vcr) +{ + vwm_xserver_t *xserver; + + assert(vcr); + assert(vcr->backend); + assert(vcr->backend->type == VCR_BACKEND_TYPE_XLIB); + + xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + XRenderFreePicture(xserver->display, vcr->xlib.grapha_picture); + XRenderFreePicture(xserver->display, vcr->xlib.graphb_picture); + XRenderFreePicture(xserver->display, vcr->xlib.tmp_a_picture); + XRenderFreePicture(xserver->display, vcr->xlib.tmp_b_picture); + XRenderFreePicture(xserver->display, vcr->xlib.text_picture); + XFreePixmap(xserver->display, vcr->xlib.text_pixmap); + XRenderFreePicture(xserver->display, vcr->xlib.shadow_picture); + XRenderFreePicture(xserver->display, vcr->xlib.picture); +} + + +/* helper for _only_ copying the xlib internal stuff embedded in the vcr, + * for resizing purposes. + */ +static void vcr_copy_xlib_internal(vcr_t *src, vcr_t *dest) +{ + vwm_xserver_t *xserver; + + assert(src); + assert(src->backend); + assert(src->backend->type == VCR_BACKEND_TYPE_XLIB); + assert(dest); + assert(dest->backend); + assert(dest->backend->type == VCR_BACKEND_TYPE_XLIB); + assert(src->backend->xlib.xserver == dest->backend->xlib.xserver); + + xserver = src->backend->xlib.xserver; + + /* XXX: note the graph pictures are copied from their current phase in the x dimension */ + XRenderComposite(xserver->display, PictOpSrc, src->xlib.grapha_picture, None, dest->xlib.grapha_picture, + src->phase, 0, /* src x, y */ + 0, 0, /* mask x, y */ + dest->phase, 0, /* dest x, y */ + src->width, src->height); + XRenderComposite(xserver->display, PictOpSrc, src->xlib.graphb_picture, None, dest->xlib.graphb_picture, + src->phase, 0, /* src x, y */ + 0, 0, /* mask x, y */ + dest->phase, 0, /* dest x, y */ + src->width, src->height); + XRenderComposite(xserver->display, PictOpSrc, src->xlib.text_picture, None, dest->xlib.text_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + src->width, src->height); + XRenderComposite(xserver->display, PictOpSrc, src->xlib.shadow_picture, None, dest->xlib.shadow_picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + src->width, src->height); + XRenderComposite(xserver->display, PictOpSrc, src->xlib.picture, None, dest->xlib.picture, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + 0, 0, /* dest x, y */ + src->width, src->height); +} +#endif /* USE_XLIB */ + + +vcr_t * vcr_free(vcr_t *vcr) +{ + if (vcr) { + assert(vcr->backend); + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: + vcr_free_xlib_internal(vcr); + break; +#endif /* USE_XLIB */ + case VCR_BACKEND_TYPE_MEM: + free(vcr->mem.bits); + free(vcr->mem.tmp); + break; + + default: + assert(0); + } + + free(vcr); + } + + return NULL; +} + + +/* resize the specified vcr's visible dimensions, which may or may not require actual + * resizing of the underlying backend resources. + * + * -errno is returned on failure (will generally be -ENOMEM), 0 returned on success with no redraw needed, + * 1 returned on success with redraw needed. + */ +int vcr_resize_visible(vcr_t *vcr, int width, int height) +{ + assert(vcr); + assert(width > 0); + assert(height > 0); + + /* nothing to do */ + if (width == vcr->visible_width && height == vcr->visible_height) + return 0; /* no redraw needed */ + + if (width <= vcr->width && height <= vcr->height) { + /* we've stayed within the current allocation, no need to involve the backend */ + vcr->visible_width = width; + vcr->visible_height = height; + /* you may be wondering how this can happen - when windows get resized smaller, we don't + * shrink the resources backing the vcr... they only grow. The shrinking just affects + * the visible_{height,width} dimensions, it doesn't resize the backend smaller. So + * when/if they grow again in visibility, it's just a matter of adjusting the visible + * dimensions, unless they exceed the maximum dimensions... which requires allocation. + */ + + return 1; /* redraw needed */ + } + + /* we're going outside the current allocation dimensions, so we need to involve the backend + * in _really_ resizing. + */ + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + vcr_t existing = *vcr; /* stow the current vcr's contents for copy+free */ + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + vcr->width = MAX(vcr->width, MAX(width, CHART_GRAPH_MIN_WIDTH)); + vcr->height = MAX(vcr->height, MAX(height, CHART_GRAPH_MIN_HEIGHT)); + + /* XXX: note this is actually _the_ place these things get allocated */ + vcr->xlib.grapha_picture = create_picture_fill(xserver, vcr->width, vcr->height, CHART_MASK_DEPTH, CPRepeat, &pa_repeat, &chart_trans_color, NULL); + vcr->xlib.graphb_picture = create_picture_fill(xserver, vcr->width, vcr->height, CHART_MASK_DEPTH, CPRepeat, &pa_repeat, &chart_trans_color, NULL); + vcr->xlib.tmp_a_picture = create_picture(xserver, vcr->width, VCR_ROW_HEIGHT, CHART_MASK_DEPTH, 0, NULL, NULL); + vcr->xlib.tmp_b_picture = create_picture(xserver, vcr->width, VCR_ROW_HEIGHT, CHART_MASK_DEPTH, 0, NULL, NULL); + + /* keep the text_pixmap reference around for XDrawText usage */ + vcr->xlib.text_picture = create_picture_fill(xserver, vcr->width, vcr->height, CHART_MASK_DEPTH, 0, NULL, &chart_trans_color, &vcr->xlib.text_pixmap); + + vcr->xlib.shadow_picture = create_picture_fill(xserver, vcr->width, vcr->height, CHART_MASK_DEPTH, 0, NULL, &chart_trans_color, NULL); + vcr->xlib.picture = create_picture(xserver, vcr->width, vcr->height, 32, 0, NULL, NULL); + + if (existing.width) { + vcr_copy_xlib_internal(&existing, vcr); + vcr_free_xlib_internal(&existing); + } + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: + /* no attempt to preserve the existing contents is done for the mem backend, + * as it's intended for a non-interactive headless use case - there is no + * resizing @ runtime. We get entered once to create the initial dimensions, + * then never recurs. + */ + assert(!vcr->mem.bits); /* since we're assuming this doesn't recur, assert it */ + vcr->mem.bits = calloc(width * height, sizeof(uint8_t)); + if (!vcr->mem.bits) + return -ENOMEM; + + assert(!vcr->mem.tmp); /* since we're assuming this doesn't recur, assert it */ + vcr->mem.tmp = calloc(width * VCR_ROW_HEIGHT, sizeof(uint8_t)); + if (!vcr->mem.tmp) { + free(vcr->mem.bits); + return -ENOMEM; + } + + { /* populate the background layer up front */ + uint8_t bg = (0x1 << VCR_LAYER_BG); + + for (int i = VCR_ROW_HEIGHT - 1; i < height; i += VCR_ROW_HEIGHT) { + uint8_t *p = &vcr->mem.bits[i * width]; + + for (int j = 0; j < width; j++, p++) + *p = bg; + } + } + + vcr->width = width; + vcr->height = height; + + break; + + default: + assert(0); + } + + vcr->visible_width = width; + vcr->visible_height = height; + + assert(vcr->width >= vcr->visible_width); + assert(vcr->height >= vcr->visible_height); + + return 0; +} + + +/* this is inspired by XDrawText and its XTextItem *items + nr_items API, + * primarily so it's easy to map the incoming call to an XDrawText call... + * but for non-xlib backends, an XDrawText equivalent will be needed. + * + * note the formatting and font-switching aspects of XTextItem have not + * been exposed here... this just takes an array of char* strings and + * turns it into an XTextItem array etc. + * + * this draws to the specified vcr layer. + * + * supply a res_width to get the rendered text width in pixels + * + * supply a negative row to suppress the actual drawing, but still get the would-be res_width + * + * x may be negative or extend outside vcr bounds, clipping will be performed as needed. + */ + /* XXX: maybe these strs should also include lengths instead of being null-terminated */ +void vcr_draw_text(vcr_t *vcr, vcr_layer_t layer, int x, int row, const vcr_str_t *strs, int n_strs, int *res_width) +{ + assert(vcr); + assert(vcr->backend); + assert(layer >= 0 && layer < VCR_LAYER_CNT); + assert(row >= 0 || res_width); + assert(strs); + assert(n_strs > 0); + /* FIXME: this should really be able to draw text into any valid layer, + * it's just the pictures/pixmaps in vcr_t aren't currently organized as an + * array easily indexed by the layer enum. TODO + */ + assert(layer == VCR_LAYER_TEXT); + + if (n_strs > VCR_DRAW_TEXT_N_STRS_MAX) + n_strs = VCR_DRAW_TEXT_N_STRS_MAX; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + XTextItem items[VCR_DRAW_TEXT_N_STRS_MAX]; + + for (int i = 0; i < n_strs; i++) { + items[i].nchars = strs[i].len; + items[i].chars = (char *)strs[i].str; + items[i].delta = 4; + items[i].font = None; + } + + if (row >= 0) { + XDrawText(vcr->backend->xlib.xserver->display, vcr->xlib.text_pixmap, vcr->backend->xlib.text_gc, + x, (row + 1) * VCR_ROW_HEIGHT - 3, /* dst x, y */ + items, n_strs); + } + + /* if the caller wants to know the width, compute it, it's dumb that XDrawText doesn't + * return the dimensions of what was drawn, fucking xlib. + */ + if (res_width) { + int width = 0; + + for (int i = 0; i < n_strs; i++) + width += XTextWidth(vcr->backend->xlib.chart_font, items[i].chars, items[i].nchars) + items[i].delta; + + *res_width = width; + } + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + + if (row >= 0 && row * VCR_ROW_HEIGHT < vcr->height) { + int y = row * VCR_ROW_HEIGHT + 3; + uint8_t mask = (0x1 << layer); + + for (int i = 0; i < n_strs && x < vcr->width; i++) { + unsigned char c; + + x += 4; /* match the delta used w/XDrawText */ + + for (int j = 0, n = 0; j < strs[i].len; j++) { + c = strs[i].str[j]; + + /* skip weird/non-printable chars */ + if (c < ' ' || c > '~') + continue; + + if (n > 0) + x += 1; + + if (x + ASCII_WIDTH >= vcr->width) { + x = vcr->width; + break; + } + + for (int k = 0; k < ASCII_HEIGHT; k++) { + for (int l = 0; l < ASCII_WIDTH; l++) { + uint8_t *p = &vcr->mem.bits[(y + k) * vcr->width + x + l]; + /* FIXME this can all be done more efficiently */ + if (x + l < 0) + continue; + + *p = (*p & ~mask) | (mask * ascii_chars[c][k * ASCII_WIDTH + l]); + } + } + + x += ASCII_WIDTH; + n++; + } + } + } + + if (res_width) { + int w = 0; + /* assume fixed 5x11 ascii glyphs */ + for (int i = 0; i < n_strs; i++) { + w += 4; /* match the delta used w/XDrawText */ + + w += strs[i].len * (ASCII_WIDTH + 1); + } + *res_width = w; + } + break; + } + + default: + assert(0); + } +} + + +/* draw an arbitrary orthonormal line into the given layer, on the xlib backend this only works + * on the VCR_LAYER_TEXT layer, so it's just asserted to only go there for now.. which + * is fine for the existing callers. + */ +/* TODO: this could have a horiz/vert flag then an offset and length, but since + * the original code was using XDrawLine directly the call sites already had + * x1,y1,x2,y2 paramaters onhand.. but we really only draw orthonormal lines, + * which enforcing simplifies ad-hoc rendering for TYPE_MEM. + */ +void vcr_draw_ortho_line(vcr_t *vcr, vcr_layer_t layer, int x1, int y1, int x2, int y2) +{ + assert(vcr); + assert(vcr->backend); + assert(layer == VCR_LAYER_TEXT); /* this is just because only the text layer has the pixmap still */ + + assert(x1 >= 0 && y1 >= 0 && x2 >= 0 && y2 >= 0); + assert(x1 == x2 || y1 == y2); /* expected always orthonormal */ + + if (x1 >= vcr->width) + x1 = vcr->width - 1; + if (x2 >= vcr->width) + x2 = vcr->width - 1; + if (y1 >= vcr->height) + y1 = vcr->height - 1; + if (y2 >= vcr->height) + y2 = vcr->height - 1; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + XDrawLine(vcr->backend->xlib.xserver->display, vcr->xlib.text_pixmap, vcr->backend->xlib.text_gc, + x1, y1, x2, y2); + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + if (x1 == x2) { + if (y1 > y2) { + int t = y1; + + y1 = y2; + y2 = t; + } + + /* vertical */ + for (uint8_t *p = &vcr->mem.bits[y1 * vcr->width + x1]; y1 <= y2; p += vcr->width, y1++) + *p |= (0x1 << layer); + + } else { + /* horizontal */ + + if (x1 > x2) { + int t = x1; + + x1 = x2; + x2 = t; + } + + for (uint8_t *p = &vcr->mem.bits[y1 * vcr->width + x1]; x1 <= x2; p++, x1++) + *p |= (0x1 << layer); + } + + break; + } + + default: + assert(0); + } + + +} + + +/* marks a "finish line" in layer for row @ current phase */ +void vcr_mark_finish_line(vcr_t *vcr, vcr_layer_t layer, int row) +{ + assert(vcr); + assert(row >= 0); + /* FIXME: the layers in backend/vcr etc should be in a layer-indexable array */ + assert(layer == VCR_LAYER_GRAPHA || layer == VCR_LAYER_GRAPHB); + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + Picture dest; + + switch (layer) { + case VCR_LAYER_GRAPHA: + dest = vcr->xlib.grapha_picture; + break; + case VCR_LAYER_GRAPHB: + dest = vcr->xlib.graphb_picture; + break; + default: + assert(0); + } + + assert(xserver); + + XRenderComposite(xserver->display, PictOpSrc, vcr->backend->xlib.finish_fill, None, dest, + 0, 0, /* src x, y */ + 0, 0, /* mask x, y */ + vcr->phase, row * VCR_ROW_HEIGHT, /* dst x, y */ + 1, VCR_ROW_HEIGHT - 1); + + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t mask = (0x1 << layer); + uint8_t *p; + + p = &vcr->mem.bits[row * VCR_ROW_HEIGHT * vcr->width + vcr->phase]; + for (int i = 0; i < VCR_ROW_HEIGHT; i++, p += vcr->width) + *p = ((*p & ~mask) | (mask * (i & 0x1))); + + break; + } + + default: + assert(0); + } +} + + +/* draw a bar at the current phase into the specified layer of t % with a minimum of min_height pixels. + * + * the only layers supported right now are grapha/graphb + */ +void vcr_draw_bar(vcr_t *vcr, vcr_layer_t layer, int row, double t, int min_height) +{ + int height, y = row * VCR_ROW_HEIGHT; + + assert(vcr); + assert(row >= 0); + assert(layer == VCR_LAYER_GRAPHA || layer == VCR_LAYER_GRAPHB); + assert(min_height >= 0 && min_height < (VCR_ROW_HEIGHT - 1)); + + if (row * VCR_ROW_HEIGHT >= vcr->height) + return; + + height = fabs(t) * (double)(VCR_ROW_HEIGHT - 1); + + if (height < min_height) + height = min_height; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + Picture *dest; + + switch (layer) { + case VCR_LAYER_GRAPHA: + dest = &vcr->xlib.grapha_picture; + break; + case VCR_LAYER_GRAPHB: + dest = &vcr->xlib.graphb_picture; + y += VCR_ROW_HEIGHT - height - 1; + break; + default: + assert(0); + } + + assert(xserver); + + XRenderFillRectangle(xserver->display, PictOpSrc, *dest, &chart_visible_color, + vcr->phase, y, /* dst x, y */ + 1, height); /* dst w, h */ + + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t *p; + + if (layer == VCR_LAYER_GRAPHB) + y += VCR_ROW_HEIGHT - height - 1; + + p = &vcr->mem.bits[y * vcr->width + vcr->phase]; + for (int i = 0; i < height; i++, p += vcr->width) + *p |= (0x1 << layer); + + break; + } + + default: + assert(0); + } +} + + +/* clear a row in the specified layer */ +/* specify negative x and width to clear the entire row, otherwise constraints the clear to x..x+width */ +/* TODO FIXME an API that allowed providing a batch of layers would work _very_ well with TYPE_MEM. */ +void vcr_clear_row(vcr_t *vcr, vcr_layer_t layer, int row, int x, int width) +{ + assert(vcr); + assert(layer < VCR_LAYER_CNT); + assert(row >= 0); + + if (x < 0) + x = 0; + + if (width < 0) + width = vcr->width; + + if (x + width > vcr->width) + width = vcr->width - x; + + assert(x + width <= vcr->width); + + if (row * VCR_ROW_HEIGHT >= vcr->height) + return; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + Picture *layers[] = { /* vcr->xlib should just have these in an array */ + &vcr->xlib.text_picture, + &vcr->xlib.shadow_picture, + &vcr->xlib.grapha_picture, + &vcr->xlib.graphb_picture, + }; + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + XRenderFillRectangle(xserver->display, PictOpSrc, *layers[layer], &chart_trans_color, + x, row * VCR_ROW_HEIGHT, /* dst x, y */ + width, VCR_ROW_HEIGHT); /* dst w, h */ + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t mask = ~((uint8_t)(0x1 << layer)); + + /* naive but correct for now - TODO: optimize */ + for (int i = 0; i < VCR_ROW_HEIGHT; i++) { + uint8_t *p = &vcr->mem.bits[(row * VCR_ROW_HEIGHT + i) * vcr->width + x]; + + for (int j = 0; j < width; j++, p++) + *p &= mask; + } + break; + } + + default: + assert(0); + } +} + + +/* copy what's below a given row up the specified amount across all the layers */ +/* XXX: note for now we expect (and assert) rows == 1, to simplify TYPE_MEM, + * this is acceptable for all existing call sites + */ +void vcr_shift_below_row_up_one(vcr_t *vcr, int row) +{ + assert(vcr); + assert(vcr->backend); + assert(row > 0); /* TODO? assert row doesn't overflow? clamp to hierarchy_end? */ + + if (row * VCR_ROW_HEIGHT >= vcr->height) + return; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + Picture *layers[] = { /* vcr->xlib should just have these in an array */ + &vcr->xlib.text_picture, + &vcr->xlib.shadow_picture, + &vcr->xlib.grapha_picture, + &vcr->xlib.graphb_picture, + }; + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + for (int layer = 0; layer < NELEMS(layers); layer++) { + XRenderChangePicture(xserver->display, *layers[layer], CPRepeat, &pa_no_repeat); + XRenderComposite(xserver->display, PictOpSrc, *layers[layer], None, *layers[layer], + 0, (1 + row) * VCR_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, row * VCR_ROW_HEIGHT, /* dest */ + vcr->width, (1 + *(vcr->hierarchy_end_ptr)) * VCR_ROW_HEIGHT - (1 + row) * VCR_ROW_HEIGHT); /* dimensions */ + XRenderChangePicture(xserver->display, *layers[layer], CPRepeat, &pa_repeat); + } + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t *dest = &vcr->mem.bits[row * VCR_ROW_HEIGHT * vcr->width]; + uint8_t *src = &vcr->mem.bits[(1 + row) * VCR_ROW_HEIGHT * vcr->width]; + size_t len = ((1 + *(vcr->hierarchy_end_ptr)) - (1 + row)) * VCR_ROW_HEIGHT * vcr->width; + + memmove(dest, src, len); + break; + } + + default: + assert(0); + } +} + + +/* copy what's below a given row down the specified amount across all layers */ +void vcr_shift_below_row_down_one(vcr_t *vcr, int row) +{ + int dest_y = (row + 1) * VCR_ROW_HEIGHT; + + assert(vcr); + assert(vcr->backend); + assert(row >= 0); + + if (dest_y >= vcr->height) + return; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + Picture *layers[] = { /* vcr->xlib should just have these in an array */ + &vcr->xlib.text_picture, + &vcr->xlib.shadow_picture, + &vcr->xlib.grapha_picture, + &vcr->xlib.graphb_picture, + }; + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + for (int layer = 0; layer < NELEMS(layers); layer++) { + XRenderComposite(xserver->display, PictOpSrc, *layers[layer], None, *layers[layer], + 0, row * VCR_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, dest_y, /* dest */ + vcr->width, vcr->height - dest_y); /* dimensions */ + } + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t *dest = &vcr->mem.bits[dest_y * vcr->width]; + uint8_t *src = &vcr->mem.bits[row * VCR_ROW_HEIGHT * vcr->width]; + size_t len = (vcr->height - dest_y) * vcr->width; + + memmove(dest, src, len); + break; + } + + default: + assert(0); + } +} + + +/* This shadows the provided layer into the shadow layer for the given row. + * Currently only layer == VCR_LAYER_TEXT is supported. + */ +void vcr_shadow_row(vcr_t *vcr, vcr_layer_t layer, int row) +{ + assert(vcr); + assert(layer == VCR_LAYER_TEXT); + assert(row >= 0); + + if (row * VCR_ROW_HEIGHT >= vcr->height) + return; + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + /* the current technique for creating the shadow is to simply render the text at +1/-1 pixel offsets on both axis in translucent black */ + XRenderComposite(xserver->display, PictOpSrc, + vcr->backend->xlib.shadow_fill, vcr->xlib.text_picture, vcr->xlib.shadow_picture, + 0, 0, + -1, row * VCR_ROW_HEIGHT, + 0, row * VCR_ROW_HEIGHT, + vcr->visible_width, VCR_ROW_HEIGHT); + + XRenderComposite(xserver->display, PictOpOver, + vcr->backend->xlib.shadow_fill, vcr->xlib.text_picture, vcr->xlib.shadow_picture, + 0, 0, + 0, -1 + row * VCR_ROW_HEIGHT, + 0, row * VCR_ROW_HEIGHT, + vcr->visible_width, VCR_ROW_HEIGHT); + + XRenderComposite(xserver->display, PictOpOver, + vcr->backend->xlib.shadow_fill, vcr->xlib.text_picture, vcr->xlib.shadow_picture, + 0, 0, + 1, row * VCR_ROW_HEIGHT, + 0, row * VCR_ROW_HEIGHT, + vcr->visible_width, VCR_ROW_HEIGHT); + + XRenderComposite(xserver->display, PictOpOver, + vcr->backend->xlib.shadow_fill, vcr->xlib.text_picture, vcr->xlib.shadow_picture, + 0, 0, + 0, 1 + row * VCR_ROW_HEIGHT, + 0, row * VCR_ROW_HEIGHT, + vcr->visible_width, VCR_ROW_HEIGHT); + + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t text_mask = (0x1 << VCR_LAYER_TEXT); + uint8_t shadow_mask = (0x1 << VCR_LAYER_SHADOW); + int vcr_width = vcr->width; + + /* TODO: optimize this */ + /* first pass has to clean up the shadow plane while doing one offset of shadow bits */ + for (int i = 1; i < VCR_ROW_HEIGHT - 1; i++) { + uint8_t *s = &vcr->mem.bits[(row * VCR_ROW_HEIGHT + i) * vcr->width + 1]; + uint8_t *d = &vcr->mem.bits[(row * VCR_ROW_HEIGHT + i) * vcr->width + 2]; + + for (int j = 0; j < vcr_width - 2; j++, s++, d++) { + uint8_t t = (*s & text_mask) << 1; /* turn text bit into shadow bit by shifting over one */ + + *d = (*d & ~shadow_mask) | t; + } + } + + /* second pass ORs the rest of the shadow bits into the now fully initialized shadow plane + * at the remaining surrounding offsets. These can all happen at once now that we can + * OR things additively. + */ + for (int i = 1; i < VCR_ROW_HEIGHT - 1; i++) { + uint8_t *s = &vcr->mem.bits[(row * VCR_ROW_HEIGHT + i) * vcr->width + 1]; + + for (int j = 0; j < vcr_width - 2; j++, s++) { + uint8_t t = (*s & text_mask); + + if (t) { + t <<= 1; /* turn text bit into shadow bit by shifting over one */ + + *(s - vcr_width) |= t; + *(s - 1) |= t; + *(s + vcr_width) |= t; + } + } + } + break; + } + + default: + assert(0); + } +} + + +/* stash the row from the specified layer in temp storage which the next unstash will copy from */ +void vcr_stash_row(vcr_t *vcr, vcr_layer_t layer, int row) +{ + assert(vcr); + assert(vcr->backend); + assert(layer == VCR_LAYER_GRAPHA || layer == VCR_LAYER_GRAPHB); + /* for now we only support stashing graphs */ + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + Picture *layers[] = { + [VCR_LAYER_GRAPHA] = &vcr->xlib.grapha_picture, + [VCR_LAYER_GRAPHB] = &vcr->xlib.graphb_picture, + }; + Picture *tmps[] = { + [VCR_LAYER_GRAPHA] = &vcr->xlib.tmp_a_picture, + [VCR_LAYER_GRAPHB] = &vcr->xlib.tmp_b_picture, + }; + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + XRenderComposite(xserver->display, PictOpSrc, *layers[layer], None, *tmps[layer], + 0, row * VCR_ROW_HEIGHT, /* src */ + 0, 0, /* mask */ + 0, 0, /* dest */ + vcr->width, VCR_ROW_HEIGHT); /* dimensions */ + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t *src = &vcr->mem.bits[row * VCR_ROW_HEIGHT * vcr->width]; + uint8_t *dest = &vcr->mem.tmp[0]; + uint8_t mask = 0x1 << layer; + + for (int i = 0; i < VCR_ROW_HEIGHT; i++) { + for (int j = 0; j < vcr->width; j++, dest++, src++) { + *dest = (*dest & ~mask) | (*src & mask); + } + } + break; + } + + default: + assert(0); + } +} + + +/* unstash the temp stored row to the destination row in the specified layer */ +void vcr_unstash_row(vcr_t *vcr, vcr_layer_t layer, int row) +{ + assert(vcr); + assert(vcr->backend); + assert(layer == VCR_LAYER_GRAPHA || layer == VCR_LAYER_GRAPHB); + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + Picture *layers[] = { + [VCR_LAYER_GRAPHA] = &vcr->xlib.grapha_picture, + [VCR_LAYER_GRAPHB] = &vcr->xlib.graphb_picture, + }; + Picture *tmps[] = { + [VCR_LAYER_GRAPHA] = &vcr->xlib.tmp_a_picture, + [VCR_LAYER_GRAPHB] = &vcr->xlib.tmp_b_picture, + }; + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + XRenderComposite(xserver->display, PictOpSrc, *tmps[layer], None, *layers[layer], + 0, 0, /* src */ + 0, 0, /* mask */ + 0, row * VCR_ROW_HEIGHT, /* dest */ + vcr->width, VCR_ROW_HEIGHT); /* dimensions */ + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t *dest = &vcr->mem.bits[row * VCR_ROW_HEIGHT * vcr->width]; + uint8_t *src = &vcr->mem.tmp[0]; + uint8_t mask = (0x1 << layer); + + for (int i = 0; i < VCR_ROW_HEIGHT; i++) { + for (int j = 0; j < vcr->width; j++, dest++, src++) { + *dest = (*dest & ~mask) | (*src & mask); + } + } + break; + } + + default: + assert(0); + } +} + + +/* for now delta is assumed to be either +1/-1, but it'd be interesting to capture delayed samples/updates + * by having a abs(delta)>1 and show them as gaps in the graph or something like that. TODO + */ +void vcr_advance_phase(vcr_t *vcr, int delta) +{ + assert(vcr); + assert(vcr->backend); + assert(delta == -1 || delta == 1); + + vcr->phase += (vcr->width + delta); + vcr->phase %= vcr->width; + + /* We clear the graphs at the new phase in preparation for the new sample being drawn, across *all* + * rows, the entire vertical slice of pixels at this phase is cleared. Just for the graph layers. + */ + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + XRenderFillRectangle(xserver->display, PictOpSrc, vcr->xlib.grapha_picture, &chart_trans_color, vcr->phase, 0, 1, vcr->height); + XRenderFillRectangle(xserver->display, PictOpSrc, vcr->xlib.graphb_picture, &chart_trans_color, vcr->phase, 0, 1, vcr->height); + + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: { + uint8_t *p = &vcr->mem.bits[vcr->phase]; + uint8_t mask = ~(uint8_t)((0x1 << VCR_LAYER_GRAPHA) | (0x1 << VCR_LAYER_GRAPHB)); + + for (int i = 0; i < vcr->height; i++, p += vcr->width) + *p &= mask; + + break; + } + + default: + assert(0); + } +} + + +/* return the # of combined hierarchy+snowflakes rows in chart */ +static inline int vcr_composed_rows(vcr_t *vcr) +{ + int snowflakes = *(vcr->snowflakes_cnt_ptr) ? 1 + *(vcr->snowflakes_cnt_ptr) : 0; /* don't include the separator row if there are no snowflakes */ + + return *(vcr->hierarchy_end_ptr) + snowflakes; +} + + +/* return the composed height of the chart */ +static inline int vcr_composed_height(vcr_t *vcr) +{ + int rows_height = vcr_composed_rows(vcr) * VCR_ROW_HEIGHT; + + return MIN(rows_height, vcr->visible_height); +} + + +/* Compose performs whatever work remains up to but not including the present-to-dest step, if there + * is any such intermediate work necessary to go from the incremental vcr state to a presentable + * form. For some backends this may do nothing at all, for others it may do a bunch of compositing + * of separate layers into a single cached layer which would then be used as the source for a + * present. + * + * This is decoupled from the present() because in cases like real-time visualization, the present may + * occur at 60FPS re-using the result of a single compose(), then compose() would occur on some lower + * frequency related to the chart update/sampling frequency, whenever the sampling was performed /and/ + * produced changes. + * + * So the compose is likely to be performed as part of the chart update, not part of the present. But + * depending on how present is implemented for a given vcr+dest, the present might implicitly perform + * a compose as part of the serialization of vcr->dest. (think a mem-vcr serializing to a png-dest, + * which might be doing some very tedious and slow row-of-pixels at a time compose while it writes the + * png file, with the mem vcr representation not having a composited result cached at all, instead always + * keeping the layers in a packed bit-planes in array of bytes form, with the vcr_compose doing effectively + * nothing for lack of memory to cache the composed results in. + * + * This can require allocations so it may fail, hence the non-voide return. + */ +int vcr_compose(vcr_t *vcr) +{ + assert(vcr); + assert(vcr->backend); + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + /* XXX: this came from charts.c::vwm_chart_compose() */ + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + int height = vcr_composed_height(vcr); + + assert(xserver); + + /* fill the chart picture with the background */ + XRenderComposite(xserver->display, PictOpSrc, vcr->backend->xlib.bg_fill, None, vcr->xlib.picture, + 0, 0, + 0, 0, + 0, 0, + vcr->visible_width, height); + + /* draw the graphs into the chart through the stencils being maintained by the sample callbacks */ + XRenderComposite(xserver->display, PictOpOver, vcr->backend->xlib.grapha_fill, vcr->xlib.grapha_picture, vcr->xlib.picture, + 0, 0, + vcr->phase, 0, + 0, 0, + vcr->visible_width, height); + XRenderComposite(xserver->display, PictOpOver, vcr->backend->xlib.graphb_fill, vcr->xlib.graphb_picture, vcr->xlib.picture, + 0, 0, + vcr->phase, 0, + 0, 0, + vcr->visible_width, height); + + /* draw the shadow into the chart picture using a translucent black source drawn through the shadow mask */ + XRenderComposite(xserver->display, PictOpOver, vcr->backend->xlib.shadow_fill, vcr->xlib.shadow_picture, vcr->xlib.picture, + 0, 0, + 0, 0, + 0, 0, + vcr->visible_width, height); + + /* render chart text into the chart picture using a white source drawn through the chart text as a mask, on top of everything */ + XRenderComposite(xserver->display, PictOpOver, vcr->backend->xlib.text_fill, vcr->xlib.text_picture, vcr->xlib.picture, + 0, 0, + 0, 0, + 0, 0, + vcr->visible_width, (*(vcr->hierarchy_end_ptr) * VCR_ROW_HEIGHT)); + + XRenderComposite(xserver->display, PictOpOver, vcr->backend->xlib.snowflakes_text_fill, vcr->xlib.text_picture, vcr->xlib.picture, + 0, 0, + 0, *(vcr->hierarchy_end_ptr) * VCR_ROW_HEIGHT, + 0, *(vcr->hierarchy_end_ptr) * VCR_ROW_HEIGHT, + vcr->visible_width, height - (*(vcr->hierarchy_end_ptr) * VCR_ROW_HEIGHT)); + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: + /* this probably doesn't do anything since mem is embedded-targed, but variants could be + * introduced in the future . + */ + break; + + default: + assert(0); + } + + return 0; +} + + +#ifdef USE_XLIB +/* this is an xlib-backend specific helper for turning the composed area into an xdamage region, + * which is primarily needed by the vwm use case. + */ +int vcr_get_composed_xdamage(vcr_t *vcr, XserverRegion *res_damaged_region) +{ + XRectangle damage = {}; + + assert(vcr); + assert(vcr->backend); + assert(vcr->backend->type == VCR_BACKEND_TYPE_XLIB); + assert(res_damaged_region); + + /* TODO: ideally this would actually be a more granular damage region constructed + * piecemeal during the compose process since the last present of the vcr to an xlib dest. + * but for now it just produces a damage region of the visible area for the chart. + */ + damage.width = vcr->visible_width; + damage.height = vcr->visible_height; + + *res_damaged_region = XFixesCreateRegion(vcr->backend->xlib.xserver->display, &damage, 1); + + return 0; +} + + +#ifdef USE_PNG +/* present the chart into a newly allocated pixmap, intended for snapshotting purposes */ +static void vcr_present_xlib_to_pixmap(vcr_t *vcr, const XRenderColor *bg_color, Pixmap *res_pixmap) +{ + static const XRenderColor blackness = { 0x0000, 0x0000, 0x0000, 0xFFFF}; + vcr_dest_t *vcr_dest; + vwm_xserver_t *xserver; + Picture dest; + + assert(vcr); + assert(vcr->backend); + assert(vcr->backend->type == VCR_BACKEND_TYPE_XLIB); + assert(res_pixmap); + + xserver = vcr->backend->xlib.xserver; + + if (!bg_color) + bg_color = &blackness; + + dest = create_picture_fill(xserver, vcr->visible_width, vcr_composed_height(vcr), 32, 0, NULL, bg_color, res_pixmap); + vcr_dest = vcr_dest_xpicture_new(vcr->backend, dest); + vcr_present(vcr, VCR_PRESENT_OP_OVER, vcr_dest, -1, -1, -1, -1); + vcr_dest = vcr_dest_free(vcr_dest); +} + + +/* present the chart the chart into an ximage we can access locally for saving as a png */ +static void vcr_present_xlib_to_ximage(vcr_t *vcr, const XRenderColor *bg_color, XImage **res_ximage) +{ + Pixmap dest_pixmap; + vwm_xserver_t *xserver; + + assert(vcr); + assert(vcr->backend); + assert(vcr->backend->type == VCR_BACKEND_TYPE_XLIB); + assert(res_ximage); + + xserver = vcr->backend->xlib.xserver; + + assert(xserver); + + vcr_present_xlib_to_pixmap(vcr, bg_color, &dest_pixmap); + *res_ximage = XGetImage(xserver->display, + dest_pixmap, + 0, + 0, + vcr->visible_width, + vcr_composed_height(vcr), + AllPlanes, + ZPixmap); + XFreePixmap(xserver->display, dest_pixmap); +} + + +/* Implements present of an xlib-backed vcr to a png dest. + * This is basically the OG pre-vcr snapshot_as_png code from vmon.c, + * meaning it makes no effort to be super conservative in terms of memory + * consumption etc. + */ +static int vcr_present_xlib_to_png(vcr_t *vcr, vcr_dest_t *dest) +{ + XImage *chart_as_ximage; + png_bytepp row_pointers; + + assert(vcr); + assert(vcr->backend); + assert(vcr->backend->type == VCR_BACKEND_TYPE_XLIB); + assert(dest); + assert(dest->type == VCR_DEST_TYPE_PNG); + assert(dest->png.output); + + vcr_present_xlib_to_ximage(vcr, NULL, &chart_as_ximage); + + row_pointers = malloc(sizeof(void *) * chart_as_ximage->height); + if (!row_pointers) { + XDestroyImage(chart_as_ximage); + + return -ENOMEM; + } + + for (unsigned i = 0; i < chart_as_ximage->height; i++) + row_pointers[i] = &((png_byte *)chart_as_ximage->data)[i * chart_as_ximage->bytes_per_line]; + + if (setjmp(png_jmpbuf(dest->png.png_ctx)) != 0) { + XDestroyImage(chart_as_ximage); + free(row_pointers); + + return -ENOMEM; + } + + /* XXX: I'm sure this is making flawed assumptions about the color format + * and type etc, but this makes it work for me and that's Good Enough for now. + * One can easily turn runtime mapping of X color formats, endianness, and packing + * details to whatever a file format like PNG can express into a tar-filled rabbithole + * of fruitless wankery. + */ + png_set_bgr(dest->png.png_ctx); + png_set_IHDR(dest->png.png_ctx, dest->png.info_ctx, + chart_as_ximage->width, + chart_as_ximage->height, + 8, + PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + png_write_info(dest->png.png_ctx, dest->png.info_ctx); + png_write_image(dest->png.png_ctx, row_pointers); + png_write_end(dest->png.png_ctx, NULL); + + XDestroyImage(chart_as_ximage); + free(row_pointers); + + return 0; +} +#endif /* USE_PNG */ + +#endif /* USE_XLIB */ + + +#ifdef USE_PNG + +/* We basically just statically define the blended outputs of all the layer combinations, + * so first we or together all the relevant palette indices for those combinations, and + * give them symbolic names to later use when populating hte palette with actual rgb values. + */ +#define VCR_TEXT (0x1 << VCR_LAYER_TEXT) +#define VCR_SHADOW (0x1 << VCR_LAYER_SHADOW) +#define VCR_GRAPHA (0x1 << VCR_LAYER_GRAPHA) +#define VCR_GRAPHB (0x1 << VCR_LAYER_GRAPHB) +#define VCR_GRAPHAB ((0x1 << VCR_LAYER_GRAPHA) | (0x1 << VCR_LAYER_GRAPHB)) +#define VCR_BG (0x1 << VCR_LAYER_BG) + +/* text over anything is going to just be white */ +#define VCR_TEXT_BG (VCR_TEXT | VCR_BG) +#define VCR_TEXT_GRAPHA (VCR_TEXT | VCR_GRAPHA) +#define VCR_TEXT_GRAPHB (VCR_TEXT | VCR_GRAPHB) +#define VCR_TEXT_GRAPHAB (VCR_TEXT | VCR_GRAPHAB) +#define VCR_TEXT_SHADOW (VCR_TEXT | VCR_SHADOW) +#define VCR_TEXT_BG_SHADOW (VCR_TEXT | VCR_BG | VCR_SHADOW) +#define VCR_TEXT_GRAPHA_SHADOW (VCR_TEXT | VCR_GRAPHA | VCR_SHADOW) +#define VCR_TEXT_GRAPHB_SHADOW (VCR_TEXT | VCR_GRAPHB | VCR_SHADOW) +#define VCR_TEXT_GRAPHAB_SHADOW (VCR_TEXT | VCR_GRAPHAB | VCR_SHADOW) + +/* shadows over graph colors get blended, otherwise they're left black */ +#define VCR_SHADOW_GRAPHA (VCR_SHADOW | VCR_GRAPHA) +#define VCR_SHADOW_GRAPHB (VCR_SHADOW | VCR_GRAPHB) +#define VCR_SHADOW_GRAPHAB (VCR_SHADOW | VCR_GRAPHAB) + +/* when without shadow */ +#define VCR_PNG_WHITE {0xff, 0xff, 0xff} +#define VCR_PNG_RED {0xff, 0x00, 0x00} +#define VCR_PNG_CYAN {0x00, 0xff, 0xff} +#define VCR_PNG_DARK_GRAY {0x20, 0x20, 0x20} + +/* when in shadow */ +#define VCR_PNG_DARK_WHITE {0x4a, 0x4a, 0x4a} +#define VCR_PNG_DARK_RED {0x80, 0x00, 0x00} +#define VCR_PNG_DARK_CYAN {0x00, 0x5b, 0x5b} + + +static int vcr_present_mem_to_png(vcr_t *vcr, vcr_dest_t *dest) +{ + static png_color pal[256] = { /* programming gfx like it's 1990 can be such a joy */ + + /* text solid white above all layers */ + [VCR_TEXT] = VCR_PNG_WHITE, + [VCR_TEXT_BG] = VCR_PNG_WHITE, + [VCR_TEXT_BG_SHADOW] = VCR_PNG_WHITE, + [VCR_TEXT_GRAPHA] = VCR_PNG_WHITE, + [VCR_TEXT_GRAPHB] = VCR_PNG_WHITE, + [VCR_TEXT_GRAPHAB] = VCR_PNG_WHITE, + [VCR_TEXT_SHADOW] = VCR_PNG_WHITE, + [VCR_TEXT_GRAPHA_SHADOW] = VCR_PNG_WHITE, + [VCR_TEXT_GRAPHB_SHADOW] = VCR_PNG_WHITE, + [VCR_TEXT_GRAPHAB_SHADOW] = VCR_PNG_WHITE, + + /* no shadow or text, plain graph colors */ + [VCR_GRAPHA] = VCR_PNG_RED, + [VCR_GRAPHB] = VCR_PNG_CYAN, + [VCR_GRAPHAB] = VCR_PNG_WHITE, + + /* shadowed same but dark */ + [VCR_SHADOW_GRAPHA] = VCR_PNG_DARK_RED, + [VCR_SHADOW_GRAPHB] = VCR_PNG_DARK_CYAN, + [VCR_SHADOW_GRAPHAB] = VCR_PNG_DARK_WHITE, + + /* the rest get defaulted to black, which is great. */ + [VCR_BG] = VCR_PNG_DARK_GRAY, + }; + + int n_rows = MIN(vcr_composed_rows(vcr), vcr->height / VCR_ROW_HEIGHT); /* prevent n_rows from overflowing the height */ + png_bytepp row_pointers; + uint8_t *row_pixels; + + row_pixels = malloc(VCR_ROW_HEIGHT * vcr->width * sizeof(uint8_t)); + if (!row_pixels) + return -ENOMEM; + + row_pointers = malloc(sizeof(void *) * VCR_ROW_HEIGHT); + if (!row_pointers) { + free(row_pixels); + return -ENOMEM; + } + + for (int i = 0; i < VCR_ROW_HEIGHT; i++) + row_pointers[i] = &((png_byte *)row_pixels)[i * vcr->width]; + + if (setjmp(png_jmpbuf(dest->png.png_ctx)) != 0) { + free(row_pixels); + free(row_pointers); + return -ENOMEM; + } + + png_set_IHDR(dest->png.png_ctx, dest->png.info_ctx, + vcr->width, /* just always use the full width/height for the file dimensions, it's annoying when comparing things to have a variety of dimensions. */ + vcr->height, + 8, + PNG_COLOR_TYPE_PALETTE, /* we use a palette for mem->png for less ram and filesize */ + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + png_set_PLTE(dest->png.png_ctx, dest->png.info_ctx, pal, NELEMS(pal)); + + /* This differs from xlib_to_png in that it presents row-at-a-time from + * the packed form @ vcr->mem.bits to dest->png_ctx. Note "row" in this + * context is a row of chart data, not a single row of pixels. + * + * This approach saves memory by not needing another WxH full copy of + * the rendered form of vcr->mem.bits for png_write_image() to access. + * But it does make the implementation a bit more tedious, and probably + * a little slower. + */ + png_write_info(dest->png.png_ctx, dest->png.info_ctx); + for (int i = 0; i < n_rows; i++) { + uint8_t *s = &vcr->mem.bits[i * VCR_ROW_HEIGHT * vcr->width]; + uint8_t *d = row_pixels; + uint8_t mask = (0x1 << VCR_LAYER_GRAPHA) | (0x1 << VCR_LAYER_GRAPHB); + + /* The graph layers need to be moved to vcr->phase, since the per-sample updates just draw + * individual graph bars without bothering to move the whole graph layer every sample. + * It makes the present more complicated / less efficient, but generally sampling is done + * more frequently. + */ + for (int j = 0; j < VCR_ROW_HEIGHT; j++) { + for (int k = 0; k < vcr->width; k++, s++, d++) { /* TODO: optimize */ + uint8_t *sg = &vcr->mem.bits[(i * VCR_ROW_HEIGHT + j) * vcr->width + ((vcr->phase + k) % vcr->width)]; + + *d = (*s & ~mask) | (*sg & mask); + + } + } + + png_write_rows(dest->png.png_ctx, row_pointers, VCR_ROW_HEIGHT); + } + + /* just black out whatever remains */ + memset(row_pixels, 0x00, vcr->width); + for (int i = n_rows * VCR_ROW_HEIGHT; i < vcr->height; i++) + png_write_row(dest->png.png_ctx, row_pointers[0]); + + png_write_end(dest->png.png_ctx, dest->png.info_ctx); + free(row_pixels); + free(row_pointers); + + return 0; +} +#endif /* USE_PNG */ + + +/* This serializes vcr's state to dest, which may be a fast and snappy operation + * like when the dest is an xwindow or xpicture from an xlib backend with a vcr + * from that same xlib backend. Or it might be a rather slow and tedious but + * memory-frugal operation like when dest is a png and vcr a mem backend, which + * targets more embedded-style memory-constrained headless use cases. + * + * Note there are coordinates/dimensions provided which most of the time will just + * match the destination, but especially in the vwm composited WM use case this + * won't be true because the dest is a picture representing the X root window. + * There we must translate the presented output within the root window wherever the + * related X window is being composited, and clip it to within the window's borders. + * + * Otherwise, we generally just spit out the entirety of the vcr's charts. + * + * providing x/y/width/height all as -1 is treated as a special case of present the whole vcr to the dest + * @ 0,0 - it's a convenience for use caases that just want to spit the full chart out to a dest containing + * nothing but the cart, so callers don't have to access+supply such things. + */ +int vcr_present(vcr_t *vcr, vcr_present_op_t op, vcr_dest_t *dest, int x, int y, int width, int height) +{ + assert(vcr); + assert(vcr->backend); + assert(dest); + + if (x == -1 && y == -1 && width == -1 && height == -1) { + x = y = 0; + width = vcr->visible_width; + height = vcr_composed_height(vcr); + } + + switch (vcr->backend->type) { +#ifdef USE_XLIB + case VCR_BACKEND_TYPE_XLIB: { + int xop; + + switch (op) { + case VCR_PRESENT_OP_SRC: + xop = PictOpSrc; + break; + case VCR_PRESENT_OP_OVER: + xop = PictOpOver; + break; + default: + assert(0); + } + switch (dest->type) { + case VCR_DEST_TYPE_XWINDOW: { + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + + /* present xlib->xwindow */ + /* vmon use case */ + + XRenderComposite(xserver->display, xop, vcr->xlib.picture, None, dest->xwindow.picture, + 0, 0, 0, 0, /* src x,y, maxk x, y */ + x, /* dst x */ + y, /* dst y */ + width, MIN(vcr_composed_height(vcr), height) /* FIXME */); /* w, h */ + break; + } + + case VCR_DEST_TYPE_XPICTURE: { + vwm_xserver_t *xserver = vcr->backend->xlib.xserver; + /* present xlib->xpicture */ + /* vwm use case */ + XRenderComposite(xserver->display, xop, vcr->xlib.picture, None, dest->xpicture.picture, + 0, 0, 0, 0, /* src x,y, maxk x, y */ + x, /* dst x */ + y, /* dst y */ + width, MIN(vcr_composed_height(vcr), height) /* FIXME */); /* w, h */ + break; + } + +#ifdef USE_PNG + case VCR_DEST_TYPE_PNG: + /* present xlib->png */ + /* this would enable snapshotting to png in vwm which isn't currently supported, + * but is also necessary to support the vmon PNG snapshotting from X use case, + * which is already supported in the pre-vcr era. + */ + return vcr_present_xlib_to_png(vcr, dest); +#endif /* USE_PNG */ + + default: + assert(0); + } + break; + } +#endif /* USE_XLIB */ + + case VCR_BACKEND_TYPE_MEM: + switch (dest->type) { +#ifdef USE_XLIB + case VCR_DEST_TYPE_XWINDOW: + /* present mem->xwindow */ + case VCR_DEST_TYPE_XPICTURE: + /* present mem->xpicture */ + VWM_ERROR("vcr_present(vcr=mem dest=x{window,picture}) unsupported"); + /* XXX: these aren't currently supported, but may be interesting to experiment + * with in the future as a low-memory real-time visualization mode, if it could + * be made efficient enough. + */ + assert(0); +#endif /* USE_XLIB */ + +#ifdef USE_PNG + case VCR_DEST_TYPE_PNG: + /* present mem->png */ + /* this is the headless vmon -> periodic png snapshots mode, + * which is the whole impetus for adding the vcr abstraction. + */ + return vcr_present_mem_to_png(vcr, dest); +#endif + default: + assert(0); + } + break; + + default: + assert(0); + } + + return 0; +} diff --git a/src/vcr.h b/src/vcr.h new file mode 100644 index 0000000..089e79a --- /dev/null +++ b/src/vcr.h @@ -0,0 +1,95 @@ +#ifndef _VCR_H +#define _VCR_H + +#include <stdio.h> /* for FILE* */ + +#ifdef USE_XLIB +#include <X11/extensions/Xrender.h> /* for Picture */ +#endif /* USE_XLIB */ + +#define VCR_DRAW_TEXT_N_STRS_MAX 512 +#define VCR_ROW_HEIGHT 15 /* this should always be larger than the font height */ + +typedef enum vcr_backend_type_t { +#ifdef USE_XLIB + VCR_BACKEND_TYPE_XLIB, +#endif /* USE_XLIB */ + VCR_BACKEND_TYPE_MEM, +} vcr_backend_type_t; + +/* there are very minimal backend events plumbed out from the X events */ +typedef enum vcr_backend_event_t { + VCR_BACKEND_EVENT_NOOP, + VCR_BACKEND_EVENT_RESIZE, + VCR_BACKEND_EVENT_REDRAW, + VCR_BACKEND_EVENT_QUIT, +} vcr_backend_event_t; + +typedef enum vcr_present_op_t { + VCR_PRESENT_OP_SRC, /* equivalent to XRender PictOpSrc */ + VCR_PRESENT_OP_OVER, /* equivaletn to XRender PictOpOver */ +} vcr_present_op_t; + +typedef enum vcr_layer_t { + VCR_LAYER_TEXT, /* the text layer for threadName/argv/wchan/state/pid etc. */ + VCR_LAYER_SHADOW, /* the shadow layer below the text (XXX: this must be kept after text) */ + VCR_LAYER_GRAPHA, /* the graph A layer below the shadow layer */ + VCR_LAYER_GRAPHB, /* the graph B layer below the shadow layer */ + VCR_LAYER_BG, /* the background layer (row separators, with milestone breaks */ +#if 0 + /* It should be reasonable to support up to eight layers, so there's room to grow. + * per-thread memory use seems like a good idea.. + */ + VCR_LAYER_UNUSED1, /* TODO */ + VCR_LAYER_UNUSED2, /* TODO */ + VCR_LAYER_UNUSED3, /* TODO */ +#endif + VCR_LAYER_CNT, +} vcr_layer_t; + +typedef struct vcr_backend_t vcr_backend_t; +typedef struct vcr_dest_t vcr_dest_t; +typedef struct vcr_t vcr_t; + +typedef struct vcr_str_t { + const char *str; + size_t len; +} vcr_str_t; + +vcr_backend_t * vcr_backend_new(vcr_backend_type_t backend, ...); +int vcr_backend_get_dimensions(vcr_backend_t *vbe, int *res_width, int *res_height); +int vcr_backend_poll(vcr_backend_t *vbe, int timeout); +vcr_backend_event_t vcr_backend_next_event(vcr_backend_t *vbe, int *res_width, int *res_height); +vcr_backend_t * vcr_backend_free(vcr_backend_t *vbe); + +#ifdef USE_XLIB +vcr_dest_t * vcr_dest_xwindow_new(vcr_backend_t *vbe, const char *name, unsigned width, unsigned height); +unsigned vcr_dest_xwindow_get_id(vcr_dest_t *dest); +vcr_dest_t * vcr_dest_xpicture_new(vcr_backend_t *vbe, Picture picture); +#endif /* USE_XLIB */ +#ifdef USE_PNG +vcr_dest_t * vcr_dest_png_new(vcr_backend_t *vbe, FILE *output); +#endif /* USE_PNG */ +vcr_dest_t * vcr_dest_free(vcr_dest_t *dest); + +vcr_t * vcr_new(vcr_backend_t *vbe, int *hierarchy_end_ptr, int *snowflakes_cnt_ptr); +vcr_t * vcr_free(vcr_t *vcr); +int vcr_resize_visible(vcr_t *vcr, int width, int height); +void vcr_draw_text(vcr_t *vcr, vcr_layer_t layer, int x, int row, const vcr_str_t *strs, int n_strs, int *res_width); +void vcr_draw_ortho_line(vcr_t *vcr, vcr_layer_t layer, int x1, int y1, int x2, int y2); +void vcr_mark_finish_line(vcr_t *vcr, vcr_layer_t layer, int row); +void vcr_draw_bar(vcr_t *vcr, vcr_layer_t layer, int row, double t, int min_height); +void vcr_clear_row(vcr_t *vcr, vcr_layer_t layer, int row, int x, int width); +void vcr_shift_below_row_up_one(vcr_t *vcr, int row); +void vcr_shift_below_row_down_one(vcr_t *vcr, int row); +void vcr_shadow_row(vcr_t *vcr, vcr_layer_t layer, int row); +void vcr_stash_row(vcr_t *vcr, vcr_layer_t layer, int row); +void vcr_unstash_row(vcr_t *vcr, vcr_layer_t layer, int row); +void vcr_advance_phase(vcr_t *vcr, int delta); +int vcr_compose(vcr_t *vcr); +#ifdef USE_XLIB +int vcr_get_composed_xdamage(vcr_t *vcr, XserverRegion *res_damaged_region); +#endif /* USE_XLIB */ +int vcr_present(vcr_t *vcr, vcr_present_op_t op, vcr_dest_t *dest, int x, int y, int width, int height); + +#endif /* _VCR_H */ @@ -16,11 +16,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <X11/Xlib.h> #include <assert.h> #include <limits.h> -#include <png.h> -#include <poll.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> @@ -33,20 +30,17 @@ #include <unistd.h> #include "charts.h" +#include "vcr.h" #include "util.h" -#include "xserver.h" /* vmon exposes the monitoring charts to the shell in an strace-like cli */ typedef struct vmon_t { - vwm_xserver_t *xserver; + vcr_backend_t *vcr_backend; + vcr_dest_t *vcr_dest; vwm_charts_t *charts; vwm_chart_t *chart; - Atom wm_protocols_atom; - Atom wm_delete_atom; - Window window; int width, height; - Picture picture; int pid; int done; int linger; @@ -54,6 +48,8 @@ typedef struct vmon_t { int snapshots_interval; int snapshot; int now_names; + int headless; + int hertz; char *output_dir; char *name; unsigned n_snapshots; @@ -163,20 +159,18 @@ static int parse_flag_str(const char * const *flag, const char * const *end, con /* set vmon->{width,height} to fullscreen dimensions */ static int set_fullscreen(vmon_t *vmon) { - XWindowAttributes wattr; - +#ifdef USE_XLIB assert(vmon); - assert(vmon->xserver); - if (!XGetWindowAttributes(vmon->xserver->display, XSERVER_XROOT(vmon->xserver), &wattr)) { - VWM_ERROR("unable to get root window attributes"); + if (vcr_backend_get_dimensions(vmon->vcr_backend, &vmon->width, &vmon->height) < 0) { + VWM_ERROR("unable to get vcr_backend dimensions"); return 0; } - vmon->width = wattr.width; - vmon->height = wattr.height; - return 1; +#else /* USE_XLIB */ + return -ENOTSUP; +#endif } @@ -188,9 +182,10 @@ static void print_help(void) " Flag Description\n" "-------------------------------------------------------------------------------\n" " -- Sentinel, subsequent arguments form command to execute\n" - " -f --fullscreen Fullscreen window\n" + " -f --fullscreen Fullscreen window (X only; no effect with --headless) \n" + " -d --headless Headless mode; no X, only snapshots (default on no-X builds)\n" " -h --help Show this help\n" - " -H --height Window height\n" + " -H --height Chart height\n" " -l --linger Don't exit when top-level process exits\n" " -n --name Name of chart, shows in window title and output filenames\n" " -N --now-names Use current time in filenames instead of start time\n" @@ -199,7 +194,7 @@ static void print_help(void) " -i --snapshots Save a PNG snapshot every N seconds (SIGUSR1 also snapshots)\n" " -s --snapshot Save a PNG snapshot upon receiving SIGCHLD\n" " -v --version Print version\n" - " -W --width Window width\n" + " -W --width Chart width\n" " -z --hertz Sample rate in hertz\n" "-------------------------------------------------------------------------------" ); @@ -316,10 +311,12 @@ static char * arg_interpolate(const vmon_t *vmon, const char *arg) } switch (c) { +#ifdef USE_XLIB case 'W': /* vmon's X window id in hex */ - fprintf(memfp, "%#x", (unsigned)vmon->window); + assert(!vmon->headless); + fprintf(memfp, "%#x", vcr_dest_xwindow_get_id(vmon->vcr_dest)); break; - +#endif case 'n': /* --name verbatim */ if (!vmon->name) { VWM_ERROR("%%n requires --name"); @@ -423,6 +420,9 @@ static int vmon_handle_argv(vmon_t *vmon, int argc, const char * const *argv) } else if (is_flag(*argv, "-N", "--now-names")) { vmon->now_names = 1; last = argv; + } else if (is_flag(*argv, "-d", "--headless")) { + vmon->headless = 1; + last = argv; } else if (is_flag(*argv, "-i", "--snapshots")) { if (!parse_flag_int(argv, end, argv + 1, 1, INT_MAX, &vmon->snapshots_interval)) return 0; @@ -446,13 +446,9 @@ static int vmon_handle_argv(vmon_t *vmon, int argc, const char * const *argv) last = argv; break; } else if (is_flag(*argv, "-z", "--hertz")) { - int hertz; - - if (!parse_flag_int(argv, end, argv + 1, 1, 1000, &hertz)) + if (!parse_flag_int(argv, end, argv + 1, 1, 1000, &vmon->hertz)) return 0; - vwm_charts_rate_set(vmon->charts, hertz); - last = ++argv; } else if (is_flag(*argv, "-h", "--help")) { print_help(); @@ -548,12 +544,11 @@ int vmon_execv(vmon_t *vmon) } -/* parse argv, connect to X, create window, attach libvmon to monitored process */ +/* parse argv, init charts/vcr_backend/vcr_dest, attach libvmon to monitored process via vwm_chart_create() */ static vmon_t * vmon_startup(int argc, const char * const *argv) { - vmon_t *vmon; - XRenderPictureAttributes pattr = {}; - XWindowAttributes wattr; + vcr_backend_type_t backend_type; + vmon_t *vmon; assert(argv); @@ -574,34 +569,44 @@ static vmon_t * vmon_startup(int argc, const char * const *argv) goto _err_free; } - vmon->xserver = vwm_xserver_open(); - if (!vmon->xserver) { - VWM_ERROR("unable to open xserver"); + if (!vmon_handle_argv(vmon, argc, argv)) { + VWM_ERROR("unable to handle arguments"); + goto _err_vcr; + } + +#ifdef USE_XLIB + if (!vmon->headless) + backend_type = VCR_BACKEND_TYPE_XLIB; +#else + /* force headless without X support */ + vmon->headless = 1; +#endif + if (vmon->headless) + backend_type = VCR_BACKEND_TYPE_MEM; + + vmon->vcr_backend = vcr_backend_new(backend_type, NULL); + if (!vmon->vcr_backend) { + VWM_ERROR("unable to create vcr backend"); goto _err_free; } - vmon->wm_delete_atom = XInternAtom(vmon->xserver->display, "WM_DELETE_WINDOW", False); - vmon->wm_protocols_atom = XInternAtom(vmon->xserver->display, "WM_PROTOCOLS", False); - - vmon->charts = vwm_charts_create(vmon->xserver); + vmon->charts = vwm_charts_create(vmon->vcr_backend); if (!vmon->charts) { VWM_ERROR("unable to create charts instance"); - goto _err_xserver; + goto _err_vcr; } - if (!vmon_handle_argv(vmon, argc, argv)) { - VWM_ERROR("unable to handle arguments"); - goto _err_xserver; - } + if (vmon->hertz) + vwm_charts_rate_set(vmon->charts, vmon->hertz); if (signal(SIGUSR1, handle_sigusr1) == SIG_ERR) { VWM_PERROR("unable to set SIGUSR1 handler"); - goto _err_xserver; + goto _err_vcr; } if (signal(SIGALRM, handle_sigusr1) == SIG_ERR) { VWM_PERROR("unable to set SIGALRM handler"); - goto _err_xserver; + goto _err_vcr; } if (vmon->snapshots_interval) { @@ -614,18 +619,19 @@ static vmon_t * vmon_startup(int argc, const char * const *argv) }, NULL); if (r < 0) { VWM_PERROR("unable to set interval timer"); - goto _err_xserver; + goto _err_vcr; } } - vmon->window = XCreateSimpleWindow(vmon->xserver->display, XSERVER_XROOT(vmon->xserver), 0, 0, vmon->width, vmon->height, 1, 0, 0); - if (vmon->name) - XStoreName(vmon->xserver->display, vmon->window, vmon->name); - XGetWindowAttributes(vmon->xserver->display, vmon->window, &wattr); - vmon->picture = XRenderCreatePicture(vmon->xserver->display, vmon->window, XRenderFindVisualFormat(vmon->xserver->display, wattr.visual), 0, &pattr); - XMapWindow(vmon->xserver->display, vmon->window); - XSelectInput(vmon->xserver->display, vmon->window, StructureNotifyMask|ExposureMask); - XSync(vmon->xserver->display, False); +#ifdef USE_XLIB + if (!vmon->headless) { + vmon->vcr_dest = vcr_dest_xwindow_new(vmon->vcr_backend, vmon->name, vmon->width, vmon->height); + if (!vmon->vcr_dest) { + VWM_ERROR("unable to create destination XWindow"); + goto _err_vcr; + } + } +#endif if (vmon->execv) { if (vmon->pid) { @@ -646,10 +652,9 @@ static vmon_t * vmon_startup(int argc, const char * const *argv) return vmon; _err_win: - XDestroyWindow(vmon->xserver->display, vmon->window); - XRenderFreePicture(vmon->xserver->display, vmon->picture); -_err_xserver: - vwm_xserver_close(vmon->xserver); + (void) vcr_dest_free(vmon->vcr_dest); +_err_vcr: + (void) vcr_backend_free(vmon->vcr_backend); _err_free: free(vmon); _err: @@ -661,12 +666,11 @@ _err: static void vmon_shutdown(vmon_t *vmon) { assert(vmon); - assert(vmon->xserver); - XDestroyWindow(vmon->xserver->display, vmon->window); vwm_chart_destroy(vmon->charts, vmon->chart); vwm_charts_destroy(vmon->charts); - vwm_xserver_close(vmon->xserver); + (void) vcr_dest_free(vmon->vcr_dest); + (void) vcr_backend_free(vmon->vcr_backend); free(vmon); } @@ -682,84 +686,9 @@ static void vmon_resize(vmon_t *vmon, int width, int height) } -static int vmon_snapshot_as_png(vmon_t *vmon, FILE *output) -{ - XImage *chart_as_ximage; - png_bytepp row_pointers; - png_infop info_ctx; - png_structp png_ctx; - - assert(vmon); - assert(output); - - vwm_chart_render_as_ximage(vmon->charts, vmon->chart, NULL, &chart_as_ximage); - - row_pointers = malloc(sizeof(void *) * chart_as_ximage->height); - if (!row_pointers) { - XDestroyImage(chart_as_ximage); - - return -ENOMEM; - } - - for (unsigned i = 0; i < chart_as_ximage->height; i++) - row_pointers[i] = &((png_byte *)chart_as_ximage->data)[i * chart_as_ximage->bytes_per_line]; - - png_ctx = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (!png_ctx) { - free(row_pointers); - XDestroyImage(chart_as_ximage); - - return -ENOMEM; - } - - info_ctx = png_create_info_struct(png_ctx); - if (!info_ctx) { - png_destroy_write_struct(&png_ctx, NULL); - XDestroyImage(chart_as_ximage); - free(row_pointers); - - return -ENOMEM; - } - - png_init_io(png_ctx, output); - - if (setjmp(png_jmpbuf(png_ctx)) != 0) { - png_destroy_write_struct(&png_ctx, &info_ctx); - XDestroyImage(chart_as_ximage); - free(row_pointers); - - return -ENOMEM; - } - - /* XXX: I'm sure this is making flawed assumptions about the color format - * and type etc, but this makes it work for me and that's Good Enough for now. - * One can easily turn runtime mapping of X color formats, endianness, and packing - * details to whatever a file format like PNG can express into a tar-filled rabbithole - * of fruitless wankery. - */ - png_set_bgr(png_ctx); - png_set_IHDR(png_ctx, info_ctx, - chart_as_ximage->width, - chart_as_ximage->height, - 8, - PNG_COLOR_TYPE_RGBA, - PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, - PNG_FILTER_TYPE_BASE); - - png_write_info(png_ctx, info_ctx); - png_write_image(png_ctx, row_pointers); - png_write_end(png_ctx, NULL); - - XDestroyImage(chart_as_ximage); - free(row_pointers); - - return 0; -} - - static int vmon_snapshot(vmon_t *vmon) { +#ifdef USE_PNG time_t now, *t_ptr = &now; struct tm *t; char t_str[32]; @@ -806,11 +735,19 @@ static int vmon_snapshot(vmon_t *vmon) if (!output) return -errno; - r = vmon_snapshot_as_png(vmon, output); - if (r < 0) { - (void) unlink(tmp_path); - (void) fclose(output); - return r; + { + vcr_dest_t *png_dest; + + png_dest = vcr_dest_png_new(vmon->vcr_backend, output); + if (!png_dest) { + (void) unlink(tmp_path); + (void) fclose(output); + return -ENOMEM; + } + + /* FIXME: render/libpng errors need to propagate and be handled */ + vwm_chart_render(vmon->charts, vmon->chart, VCR_PRESENT_OP_SRC, png_dest, -1, -1, -1, -1); + png_dest = vcr_dest_free(png_dest); } fflush(output); @@ -821,50 +758,48 @@ static int vmon_snapshot(vmon_t *vmon) return -errno; return 0; +#else + return -ENOTSUP; +#endif } - -/* handle the next X event, may block */ +/* handle the next backend event, may block */ static void vmon_process_event(vmon_t *vmon) { - XEvent ev; + int width, height; + vcr_backend_event_t ev; assert(vmon); - XNextEvent(vmon->xserver->display, &ev); - - switch (ev.type) { - case ConfigureNotify: - vmon_resize(vmon, ev.xconfigure.width, ev.xconfigure.height); - vwm_chart_compose(vmon->charts, vmon->chart, NULL); - vwm_chart_render(vmon->charts, vmon->chart, PictOpSrc, vmon->picture, 0, 0, vmon->width, vmon->height); + ev = vcr_backend_next_event(vmon->vcr_backend, &width, &height); + switch (ev) { + case VCR_BACKEND_EVENT_RESIZE: + vmon_resize(vmon, width, height); + vwm_chart_compose(vmon->charts, vmon->chart); + vwm_chart_render(vmon->charts, vmon->chart, VCR_PRESENT_OP_SRC, vmon->vcr_dest, -1, -1, -1, -1); break; - case Expose: - vwm_chart_render(vmon->charts, vmon->chart, PictOpSrc, vmon->picture, 0, 0, vmon->width, vmon->height); + case VCR_BACKEND_EVENT_REDRAW: + vwm_chart_render(vmon->charts, vmon->chart, VCR_PRESENT_OP_SRC, vmon->vcr_dest, -1, -1, -1, -1); break; - case ClientMessage: - if (ev.xclient.message_type != vmon->wm_protocols_atom) - break; - - if (ev.xclient.data.l[0] != vmon->wm_delete_atom) - break; - + case VCR_BACKEND_EVENT_QUIT: vmon->done = 1; break; + case VCR_BACKEND_EVENT_NOOP: + break; + default: - VWM_TRACE("unhandled event: %x\n", ev.type); + VWM_TRACE("unhandled event: %x\n", ev); } } int main(int argc, const char * const *argv) { - vmon_t *vmon; - struct pollfd pfd = { .events = POLLIN }; - int ret = EXIT_SUCCESS; + vmon_t *vmon; + int ret = EXIT_SUCCESS; vmon = vmon_startup(argc, argv); if (!vmon) { @@ -872,17 +807,21 @@ int main(int argc, const char * const *argv) return EXIT_FAILURE; } - pfd.fd = ConnectionNumber(vmon->xserver->display); - while (!vmon->done) { int delay; + /* update only actually updates when enough time has passed, and always returns how much time + * to sleep before calling update again (-1 for infinity (paused)). + * + * if 0 is returned, no update was performed/no changes occured. + */ if (vwm_charts_update(vmon->charts, &delay)) { - vwm_chart_compose(vmon->charts, vmon->chart, NULL); - vwm_chart_render(vmon->charts, vmon->chart, PictOpSrc, vmon->picture, 0, 0, vmon->width, vmon->height); + vwm_chart_compose(vmon->charts, vmon->chart); + if (!vmon->headless) + vwm_chart_render(vmon->charts, vmon->chart, VCR_PRESENT_OP_SRC, vmon->vcr_dest, -1, -1, -1, -1); } - if (XPending(vmon->xserver->display) || poll(&pfd, 1, delay) > 0) + if (vcr_backend_poll(vmon->vcr_backend, delay) > 0) vmon_process_event(vmon); if (got_sigint > 2 || got_sigquit > 2) { @@ -898,10 +837,10 @@ int main(int argc, const char * const *argv) } if (got_sigchld) { - int status; + int status, r; - if (vmon->snapshot && vmon_snapshot(vmon) < 0) - VWM_ERROR("error saving snapshot"); + if (vmon->snapshot && (r = vmon_snapshot(vmon)) < 0) + VWM_ERROR("error saving snapshot: %s", strerror(-r)); got_sigchld = 0; wait(&status); @@ -914,9 +853,11 @@ int main(int argc, const char * const *argv) } } - if (got_sigusr1) { - if (vmon_snapshot(vmon) < 0) - VWM_ERROR("error saving snapshot"); + if (got_sigusr1 || (vmon->snapshots_interval && !vmon->n_snapshots)) { + int r; + + if ((r = vmon_snapshot(vmon)) < 0) + VWM_ERROR("error saving snapshot: %s", strerror(-r)); got_sigusr1 = 0; } @@ -82,11 +82,16 @@ static vwm_t * vwm_startup(void) goto _err_free; } - if (!(vwm->charts = vwm_charts_create(vwm->xserver))) { - VWM_ERROR("Failed to create charts"); + if (!(vwm->vcr_backend = vcr_backend_new(VCR_BACKEND_TYPE_XLIB, vwm->xserver))) { + VWM_ERROR("Failed to create vcr backend"); goto _err_xclose; } + if (!(vwm->charts = vwm_charts_create(vwm->vcr_backend))) { + VWM_ERROR("Failed to create charts"); + goto _err_vbe; + } + /* query the needed X extensions */ if (!XQueryExtension(VWM_XDISPLAY(vwm), COMPOSITE_NAME, &composite_opcode, &composite_event, &composite_error)) { VWM_ERROR("No composite extension available"); @@ -173,6 +178,9 @@ static vwm_t * vwm_startup(void) _err_charts: vwm_charts_destroy(vwm->charts); +_err_vbe: + vcr_backend_free(vwm->vcr_backend); + _err_xclose: vwm_xserver_close(vwm->xserver); @@ -8,6 +8,7 @@ #include "list.h" #include "charts.h" #include "util.h" +#include "vcr.h" #include "xserver.h" #define WINDOW_BORDER_WIDTH 1 @@ -43,6 +44,7 @@ typedef enum _vwm_context_color_t { typedef struct _vwm_t { vwm_xserver_t *xserver; /* global xserver instance */ + vcr_backend_t *vcr_backend; /* global vcr backend for charts */ vwm_charts_t *charts; /* golbal charts instance */ /* extra X stuff needed by vwm */ |