summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2024-07-15 00:35:15 -0700
committerVito Caputo <vcaputo@pengaru.com>2024-08-13 23:36:43 -0700
commit9b05c41168842035ddcd377ed5e23bb862fb4a60 (patch)
tree60c480f891ed2a256c7c91083e7385f6e8666a86 /src
parent94a7020ad8c9efd9c5818eb3422ff4cb66a1b278 (diff)
charts: first stab at factoring out Xlib from charts/vmon
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am30
-rw-r--r--src/ascii.c1228
-rw-r--r--src/ascii.h9
-rw-r--r--src/charts.c646
-rw-r--r--src/charts.h24
-rw-r--r--src/composite.c8
-rw-r--r--src/vcr.c2176
-rw-r--r--src/vcr.h95
-rw-r--r--src/vmon.c293
-rw-r--r--src/vwm.c12
-rw-r--r--src/vwm.h2
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 */
diff --git a/src/vmon.c b/src/vmon.c
index 3977be0..7eac119 100644
--- a/src/vmon.c
+++ b/src/vmon.c
@@ -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;
}
diff --git a/src/vwm.c b/src/vwm.c
index f52df39..c6d1405 100644
--- a/src/vwm.c
+++ b/src/vwm.c
@@ -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);
diff --git a/src/vwm.h b/src/vwm.h
index fec6a58..21e73a7 100644
--- a/src/vwm.h
+++ b/src/vwm.h
@@ -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 */
© All Rights Reserved