~charles/dotfiles

bec398ad3e7e3ff5813a560191404ba525f0271d — Charles Daniels 3 years ago 2dd186b
added nowall
8 files changed, 328 insertions(+), 339 deletions(-)

M .gitignore
M install.sh
A nowall/Makefile
A nowall/nowall.c
A nowall/nowall.h
D overlay/bin/burnwallpaper
M overlay/bin/restore-sanity
D overlay/bin/setwallpaper
M .gitignore => .gitignore +3 -0
@@ 10,3 10,6 @@ tasks/*/.gitignore
doc/source/tasks
doc/source/_static/*overview*.svg
doc/build
nowall/nowall
nowall/*.gch
nowall/*.o

M install.sh => install.sh +7 -0
@@ 79,4 79,11 @@ echo "    email = $git_email" >> "$git_config_file"
echo "" >> "$git_config_file"
cat "$DOTFILES_DIR/gitconfig" >> "$git_config_file"

# setup nowall
cd "$DOTFILES_DIR/nowall"
make
cp ./nowall ~/bin/nowall



exit 0

A nowall/Makefile => nowall/Makefile +9 -0
@@ 0,0 1,9 @@
CFLAGS=-Wall -Wextra -pedantic -std=c89 -Og -g

.PHONY: clean

nowall: nowall.c nowall.h
	$(CC) $(CFLAGS) $$(pkg-config --cflags --libs x11) $< -o $@

clean:
	rm -rf nowall *.gch *.o

A nowall/nowall.c => nowall/nowall.c +269 -0
@@ 0,0 1,269 @@
#include "nowall.h"

/**
 * @brief Get the current time and run it through strftime with fmt as the
 * format string.
 *
 * https://www.tutorialspoint.com/c_standard_library/c_function_strftime.htm
 *
 * @param fmt
 *
 * @return
 */
char* get_time(char* fmt) {
	time_t rawtime;
	struct tm *info;
	char* buffer;

	buffer = (char*) malloc(NOWALL_TIMESTAMP_MAXLEN * sizeof(char));

	/* get the current time */
	time(&rawtime);
	info = localtime(&rawtime);

	strftime(buffer, NOWALL_TIMESTAMP_MAXLEN, fmt, info);

	return buffer;
}

/**
 * @brief Get current memory usage as a percentage (0 .. 1.0)
 *
 * @return
 */
double get_memory_usage() {
	/* sysconf(_SC_PAGE_SIZE); */
	return 1.0f - (1.0f * sysconf(_SC_AVPHYS_PAGES)) / (1.0f * sysconf(_SC_PHYS_PAGES));
}

/**
 * @brief Generate the message string
 *
 * @return
 */
char* generate_message() {
	char* msg;
	double load_avg[3];
	double memory_usage;

	msg = (char*) malloc(sizeof(char) * NOWALL_MESSAGE_MAXLEN);

	/* get load average */
	getloadavg(load_avg, 3);

	/* get memory usage */
	memory_usage = get_memory_usage() * 100.0f;

	snprintf(
			msg,
			NOWALL_MESSAGE_MAXLEN,
			"%0.2f / %0.0f%% / %s",
			load_avg[0],
			memory_usage,
			get_time("%m-%d")
	);

	return msg;


}

/**
 * @brief calculate the current score for the history vector
 *
 * @return
 */
float calculate_score() {
	double load_avg[3];
	double memory_usage;

	/* get load average */
	getloadavg(load_avg, 3);

	/* get memory usage */
	memory_usage = get_memory_usage();

	/* we normalize the load average by the number of CPU cores which
	 * are online at this moment */
	return 0.5 * memory_usage + \
		0.5 * load_avg[0] / sysconf(_SC_NPROCESSORS_ONLN);
}

void draw_frame(Display* disp, int screen_num, float* history,
		XColor bg_color, XColor fg_color, XColor text_color) {

	Window root;
	Screen* screen;
	Colormap cmap;
	Pixmap pmap;
	Atom prop;
	GC gc;
	unsigned int depth;
	char* msg;
	int history_index = 0;
	double x_base, y_base, x_centered, y_centered, horizontal_pos;
	double horizontal_offset, bar_height, msg_x;

	/* gather information about the display and screen to manipulate */
	root   = RootWindow(disp, screen_num);		/* root window      */
	screen = ScreenOfDisplay(disp, screen_num);	/* screen "object"  */
	cmap   = DefaultColormap(disp, screen_num);	/* screen color map */
	depth  = DefaultDepth(disp, screen_num);	/* screen depth     */

	/* allocate provided color values or die */
	SAFE_ALLOC_COLOR(disp, cmap, &fg_color);
	SAFE_ALLOC_COLOR(disp, cmap, &bg_color);
	SAFE_ALLOC_COLOR(disp, cmap, &text_color);

	/* create the pixmap - this serves as the intermediary between our GC
	 * and the root window */
	pmap = XCreatePixmap(
			disp,			/* display */
			root,			/* drawable */
			screen->width,		/* width */
			screen->height,		/* height */
			depth			/* depth */
	);

	/* create the graphics context for us to draw into */
	gc = XCreateGC(disp, pmap, 0, NULL);

	/* fill in the background */
	XSetForeground(disp, gc, bg_color.pixel);
	XFillRectangle(
			disp,		/* display */
			pmap,		/* drawable */
			gc,		/* graphics context */
			0,		/* x position */
			0,		/* y position */
			screen->width,	/* width */
			screen->height	/* height */
	);

	/* draw the text */
	XSetForeground(disp, gc, text_color.pixel);
	msg = generate_message();
	msg_x = screen->width / 2 - (strlen(msg) * NOWALL_HORIZ_PIX_PER_CHAR / 2);
	XDrawString(
			disp,			/* display */
			pmap,			/* drawable */
			gc,			/* graphics context */
			msg_x,			/* x position */
			screen->height / 1.8,	/* y position */
			msg,			/* string */
			strlen(msg)		/* string length */
	);

	/* draw the history visualization */
	XSetForeground(disp, gc, fg_color.pixel);
	for (history_index = 0;
		history_index < NOWALL_HISTORY_LEN;
		history_index ++) {

		/* note that x, y identifies the upper left of the rectangle */

		horizontal_pos = history_index - (NOWALL_HISTORY_LEN / 2);
		horizontal_offset = \
			horizontal_pos * (NOWALL_HISTORY_BAR_WIDTH + \
					NOWALL_HISTORY_BAR_SEP);
		x_base = screen->width / 2 + horizontal_offset;
		y_base = screen->height / 2.2;
		bar_height = screen->height * 0.1 * history[history_index];
		x_centered = x_base + NOWALL_HISTORY_BAR_WIDTH / 2;
		y_centered = y_base - bar_height / 2;


		XFillRectangle(
				disp,		/* display */
				pmap,		/* drawable */
				gc,		/* graphics context */
				x_centered,	/* x position */
				y_centered,	/* y position */
				NOWALL_HISTORY_BAR_WIDTH,	/* width */
				bar_height	/* height */
		);

	}

	/* set the root window background to what we just drew */
	XSetWindowBackgroundPixmap(disp, root, pmap);

	/* do the right magic to keep what we draw persistent even after
	 * we exit */
	prop = XInternAtom(disp, "_XROOTPMAP_ID", False);
	XChangeProperty(
			disp,				/* display    */
			root,				/* window     */
			prop,				/* properties */
			XA_PIXMAP,			/* type       */
			32,				/* format     */
			PropModeReplace,		/* mode       */
			(unsigned char *) &pmap,	/* data       */
			1				/* nelements  */
	);

	/* tidy up */
	XFreeGC(disp, gc);
	XClearWindow(disp, root);
	XSetCloseDownMode(disp, RetainPermanent);


}

int main(void) {
	Display *dpy;
	int screen;
	XColor bg_color;
	XColor fg_color;
	XColor text_color;
	float history[NOWALL_HISTORY_LEN];
	int history_index;

	/* detect xorg information */
	dpy = XOpenDisplay(NULL);
	if (!dpy) {
		fprintf(stderr, "failed to open display!\n");
		exit (2);
	}
	screen = DefaultScreen(dpy);

	/* setup colors */
	bg_color.red   = 0 << 8;
	bg_color.blue  = 0 << 8;
	bg_color.green = 0 << 8;

	text_color.red   = 128 << 8;
	text_color.blue  = 128 << 8;
	text_color.green = 128 << 8;

	fg_color.red   = 128 << 8;
	fg_color.blue  = 128 << 8;
	fg_color.green = 128 << 8;

	/* initialize the history vector */
	for (history_index = 0 ;
		history_index < NOWALL_HISTORY_LEN;
		history_index ++) {
		history[history_index] = 0;
	}

	while (1) {
		/* update history vector */
		history[NOWALL_HISTORY_LEN-1] = calculate_score();
		for (history_index = 0 ;
			history_index < (NOWALL_HISTORY_LEN - 1);
			history_index ++) {
			history[history_index] = history[history_index + 1];
		}

		/* update rendering */
		draw_frame(dpy, screen, history,
			bg_color, fg_color, text_color);

		sleep(NOWALL_INTERVAL);
	}

	XCloseDisplay(dpy);

	return 0;
}

A nowall/nowall.h => nowall/nowall.h +38 -0
@@ 0,0 1,38 @@
#ifndef NOWALL_H
#define NOWALL_H

/* #define _POSIX_C_SOURCE 200112L */
#define _DEFAULT_SOURCE 1

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define SAFE_ALLOC_COLOR(disp, cmap, color) \
	if (XAllocColor(disp, cmap, color) == 0) { \
		fprintf(stderr, "FATAL: failed to allocate color\n"); \
		exit(1); \
	}

#define NOWALL_TIMESTAMP_MAXLEN 128
#define NOWALL_MESSAGE_MAXLEN 512
#define NOWALL_HISTORY_LEN 25
#define NOWALL_HISTORY_BAR_WIDTH 1
#define NOWALL_HISTORY_BAR_SEP 18
#define NOWALL_HORIZ_PIX_PER_CHAR 6
#define NOWALL_INTERVAL 5

char* get_time(char* fmt);
double get_memory_usage();
char* generate_message();
float calculate_score();
void draw_frame(Display* disp, int screen_num, float* history,
		XColor bg_color, XColor fg_color, XColor text_color);
int main(void);

#endif

D overlay/bin/burnwallpaper => overlay/bin/burnwallpaper +0 -290
@@ 1,290 0,0 @@
#!/usr/bin/env bash

# .SCRIPTDOC

# This script burns information (configurable by editing the body of the sript)
# into an image (generally a wallpaper), and either writes out that image, or
# sets it as the wallpaper using ``feh``.

# .SYNTAX

# $1 . . . path to input image

# $2 . . . (optional) path to write output image to. If omitted, the output is
#          set using feh instead.

# .LICENSE
#
# Copyright (c) 2018, Charles Daniels
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# .ENDOC

#### helper methods ###########################################################

pct2pix () {
	# Accept an col,row coordinate (on $1, $2) as and integer percentage in
	# 0..100, convert this to an absolute pixel coordinate in the image and
	# output it comma-delimited (for easy consumption by convert).

	IMAGE_HEIGHT="$(identify -format "%h" "$INPUT")"
	IMAGE_WIDTH="$(identify -format "%w" "$INPUT")"

	ROW="$(echo "($IMAGE_HEIGHT  / 100) * $2" | bc)"
	COL="$(echo "($IMAGE_WIDTH / 100) * $1" | bc)"

	printf "%s,%s" $COL $ROW
}

vpad () {
	# Pad $1 with newlines until it is $2 many lines long.

	TEXT="$1"
	DESIRED_LENGTH="$2"
	CURRENT_LENGTH="$(echo -e "$TEXT" | wc -l)"
	DIFFERENCE="0"
	if [ "$DESIRED_LENGTH" -gt "$CURRENT_LENGTH" ] ; then
		# if the current length is shorter than the desired length,
		# then we calculate the difference
		DIFFERENCE="$(echo "$DESIRED_LENGTH - $CURRENT_LENGTH" | bc)"
	fi

	# we use '\n ' instead of just \n to prevent BASH from discarding the
	# blank lines during variable substitution.
	PADDING="$(for i in $(seq $DIFFERENCE) ; do echo -n "\n " ; done)"

	echo -e -n "$TEXT$PADDING"
}

draw () {
	# Generate a -draw argument to convert.
	#
	# $1 . . . horizontal position as a percentage in 0..100
	#
	# $2 . . . vertical position as a percentage in 0..100
	#
	# $3 . . . text

	printf "%s" "-draw "
	printf "%s" '"'
	printf "fill-opacity %s " $OPACITY
	printf "text "
	pct2pix $1 $2
	printf " '%s'" "$(echo -e -n "$3")"
	printf "%s" '"'
}


#### setup ####################################################################

set -u
set -e

if [ $# -lt 1 ] ; then
	echo "$(basename $0) [input] [output]" > /dev/stderr
	exit 1
fi

INPUT="$1"

OUTPUT="/tmp/$(uuidgen)"
OUTPUT_IS_TEMP="YES"
SET_WALLPAPER="YES"
if [ $# -eq 2 ] ; then
	# set the output file if it was provided
	OUTPUT="$2"
	# prevent the output from being deleted
	OUTPUT_IS_TEMP="NO"
	# prevent the output from being immediate set as the wallpaper
	SET_WALLPAPER="NO"

fi

# select the font file from the output of fc-list
FONT="$(fc-list  | grep -i deja | grep -i mono | grep -i book | cut -d: -f1)"

# TODO: this should be expressed as a percentage of image height
FONTSIZE=30

OPACITY=0.7
FILLCOLOR=white

CACHE_DIR="$HOME/.cache/burnwallpaper"
mkdir -p "$CACHE_DIR"

#### collect data to be displayed #############################################

# fetch a weather report from wttr.in. The sed command strips out terminal
# color codes, which `convert` does not understand. The second sed command
# escapes instances of the ` symbol, which can cause trouble when the
# convert command is executed with sh -c.

WEATHER_FILE="$CACHE_DIR/weather.txt"

# handle case where WEATHER_FILE does not exist
WEATHER_FILE_TIME=2000
if [ -e "$WEATHER_FILE" ] ; then
	WEATHER_FILE_TIME=$(stat "$WEATHER_FILE" -c %Y)
fi

WEATHER_FILE_AGE=$(expr $(date +%s) - $WEATHER_FILE_TIME)

# if the weather is more than 10 minutes out of date, fetch it again
if [ $WEATHER_FILE_AGE -gt 600 ] ; then

	echo "INFO: fetching weather... "
	WEATHER="$(curl -s wttr.in | sed 's/\x1B\[[0-9;]\+[A-Za-z]//g' | sed 's/`/\\`/g' | head -n 7)"
	if [ "$WEATHER" = "" ] ; then
		# handle network outage
		WEATHER="<network outage>"
	else
		# only store the weather if there was no network issue
		echo "$WEATHER" > "$WEATHER_FILE"
	fi
else
	echo "INFO: weather file is $WEATHER_FILE_AGE seconds old, which is still current"
	WEATHER="$(cat "$WEATHER_FILE")"
fi
WEATHER="$(vpad "$WEATHER" 9)"

# We eliminate entries for snaps, as this produces too many lines of output
echo "INFO: generating block device listing... "
BLK="$(lsblk | grep -v '\/snap')"


HN_FILE="$CACHE_DIR/hn.txt"
HN=""

# handle case where HN_FILE does not exist yet
HN_FILE_TIME=2000
if [ -e "$HN_FILE" ] ; then
	HN_FILE_TIME=$(stat "$HN_FILE" -c %Y)
fi

HN_FILE_AGE=$(expr $(date +%s) - $HN_FILE_TIME)

# check if HN file has changed in the last 30 minutes, and if so just use it's
# contents
#
if [ $HN_FILE_AGE -gt 1800 ] ; then
	echo "INFO: HN file is out of date, regenerating... "

	# fetch and extract post titles
	POST_TITLES="$(query-webpage \
		--url 'https://news.ycombinator.com/' \
		--query '//a[contains(@class, "storylink")]' \
		--extract | head -n 5)"

	if [ "$POST_TITLES" = "" ] ; then
		# handle network outage, just use whatever was in the
		# HN file last.
		HN="$(cat "$HN_FILE")"
		echo "INFO: failed to retrieve HN posts"
	else
		# add an extra newline after every post title and wrap it to 40
		# columns. The sed command escapes any instances of the ' character
		# in the post title, which would otherwise break convert's argument
		# parsing.
		HN="$(echo "$POST_TITLES" | awk '{print ; print "";}' | fold -w 40 -s | sed "s/'/\\\'/g")"
		echo "$HN" > "$HN_FILE"
	fi
else
	echo "INFO: HN file is $HN_FILE_AGE seconds old, which is still current"
	HN="$(cat "$HN_FILE")"
fi

# generate calendar ; we drop the first line and replace it with the
# current date, as there is not a good way to highlight the current date.
echo "INFO: generating calendar... "

# generate ncal output, and put brackets around the current day
CURRENT_DOM="$(date +"%-d")"
CALENDAR="$(ncal -M -h -b | sed "s/ $CURRENT_DOM /[$CURRENT_DOM]/g")"
CALENDAR="$(vpad "$CALENDAR" 9)"

# hostname and IP address
echo "INFO: gathering host information... "
HOST_INFO="$(whoami)@$(hostname) ($(get-ip))"
HOST_INFO="$(vpad "$HOST_INFO" 3)"

# uptime
echo "INFO: gathering uptime... "
# these sed commands replace every ',  ' with a newline, then delete leading
# whitespace, this produces a nice multi-line left-justified output.
UPTIME="$(uptime | sed 's/,  /\n/g' | sed 's/^ //g')"
UPTIME="$(vpad "$UPTIME" 5)"

# get mpc status
# TODO: would be cool to have this fetch the album art and burn that in also
echo "INFO: fetching MPC status"
MPC_STATUS="$(mpc status | head -n 2)"
if [ "$(echo "$MPC_STATUS" | wc -l)" -lt 2 ] ; then
	# handle case where no music is playing with MPC
	MPC_STATUS="<not playing>"
fi
MPC_STATUS="$(vpad "$MPC_STATUS" 4)"

# generate columns
COL_LEFT="$CALENDAR\n$WEATHER\n$BLK"
COL_RIGHT="$HOST_INFO\n$UPTIME\n$MPC_STATUS\n$HN"


#### generate output ##########################################################

echo "INFO: generating convert command... "
# setup convert command
CONVERT_CMD="convert"
CONVERT_CMD="$CONVERT_CMD -font '$FONT' "
CONVERT_CMD="$CONVERT_CMD -pointsize $FONTSIZE"
CONVERT_CMD="$CONVERT_CMD -fill '$FILLCOLOR'"

# insert draw calls
CONVERT_CMD="$CONVERT_CMD $(draw 8 10 "$COL_LEFT")"
CONVERT_CMD="$CONVERT_CMD $(draw 65 10 "$COL_RIGHT")"

# setup input and output file
CONVERT_CMD="$CONVERT_CMD '$INPUT' '$OUTPUT'"

echo "INFO: generated command: $CONVERT_CMD"

# we can't just use a bare $CONVERT_CMD because of string quoting issues
echo "INFO: executing convert... "
sh -c "$CONVERT_CMD"

#### clean up #################################################################

if [ "$SET_WALLPAPER" = "YES" ] ; then
	echo "INFO: setting wallpaper... "
	feh --bg-fill "$OUTPUT"
fi

if [ "$OUTPUT_IS_TEMP" = "YES" ] ; then
	echo "INFO: cleaning up... "
	rm -f "$OUTPUT"
fi

M overlay/bin/restore-sanity => overlay/bin/restore-sanity +2 -1
@@ 18,10 18,10 @@

# .ENDOC

~/bin/setwallpaper
xmodmap ~/.Xmodmap
killall mate-settings-daemon >/dev/null 2>&1 | true
killall mate-screensaver > /dev/null 2>&1 | true
killall nowall
numlockx on

if [ -x "$(which redshift-qt)" ] ; then


@@ 34,3 34,4 @@ elif [ -x "$(which redshift-gtk)" ] ; then
	redshift-gtk > /dev/null 2>&1 &
fi
~/bin/system-launch-compositor
disavow ~/bin/nowall

D overlay/bin/setwallpaper => overlay/bin/setwallpaper +0 -48
@@ 1,48 0,0 @@
#!/usr/bin/env bash

# .SCRIPTDOC

# Generate a new wallpaper with burnwallpaper, then set it as the current
# wallpaper.

# .LICENSE
#
# Copyright (c) 2018, Charles Daniels
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# .ENDOC

set -u
set -e

# generate wallpaper
rm -f ~/.wallpapers/burned
~/bin/burnwallpaper ~/.wallpapers/current ~/.wallpapers/burned

feh --bg-fill ~/.wallpapers/burned