diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..b0ca175 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,36 @@ +name: Tests +on: + push: + schedule: + - cron: "0 0 7 * *" + +jobs: + build: + strategy: + matrix: + image: [ "ubuntu:latest", "ubuntu:22.04", "archlinux:latest" ] + continue-on-error: true + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + steps: + - name: Checkout prepare script + uses: actions/checkout@v4 + with: + sparse-checkout: prepare.sh + sparse-checkout-cone-mode: false + + - name: Prepare build environment + run: ./prepare.sh -u | tee -a "$GITHUB_ENV" + + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Correct ownership of repository + run: chown -R $(id -u):$(id -g) . + + - name: Build + run: make build + + - name: Check README.md + run: tools/is-clean --make --root=doc README.md diff --git a/README.md b/README.md index 2f3c82e..05c23ef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ displayswitcheroo ================= +[![Tests](https://github.com/rootmos/displayswitcheroo/actions/workflows/tests.yaml/badge.svg)](https://github.com/rootmos/displayswitcheroo/actions/workflows/tests.yaml) + A [Lua](https://lua.org) c-module wrapping the [XRandr](https://www.x.org/wiki/Projects/XRandR/) X11 extension, and a bundled command-line application with the ~~batteries~~ module included. diff --git a/doc/.gitignore b/doc/.gitignore index b0acc35..cf1adad 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,2 +1,3 @@ usage.* output.* +!output.list diff --git a/doc/Makefile b/doc/Makefile index a893689..bf92c7a 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,5 +1,5 @@ THIS := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) -ROOT ?= $(realpath $(THIS)/..) +ROOT ?= .. TOOLS ?= $(ROOT)/tools export EXE ?= $(ROOT)/src/cli.exe diff --git a/doc/README.in.md b/doc/README.in.md index 86936ea..9b83b56 100644 --- a/doc/README.in.md +++ b/doc/README.in.md @@ -1,6 +1,8 @@ displayswitcheroo ================= +[![Tests](https://github.com/rootmos/displayswitcheroo/actions/workflows/tests.yaml/badge.svg)](https://github.com/rootmos/displayswitcheroo/actions/workflows/tests.yaml) + A [Lua](https://lua.org) c-module wrapping the [XRandr](https://www.x.org/wiki/Projects/XRandR/) X11 extension, and a bundled command-line application with the ~~batteries~~ module included. diff --git a/doc/output.list b/doc/output.list new file mode 100644 index 0000000..b803d4c --- /dev/null +++ b/doc/output.list @@ -0,0 +1,60 @@ +screen 0: 1920x1080 (min 320x200, max 16384x16384) (531mm x 299mm) (window 1946) +output HDMI-1 +output eDP-1: (309mm x 173mm) (fpr 0x57a9d7d6) + 1920x1080 59.98+ 59.97d 59.96 59.93 + 1680x1050 59.95 59.88 + 1400x1050 59.98 + 1600x900 59.99d 59.94d 59.95 59.82 + 1280x1024 60.02 + 1400x900 59.96 59.88 + 1280x960 60.00 + 1440x810 60.00d 59.97d + 1368x768 59.88 59.85 + 1280x800 59.99d 59.97d 59.81 59.91 + 1280x720 60.00d 59.99d 59.86 59.74 + 1024x768 60.04d 60.00 + 960x720 60.00d + 928x696 60.05d + 896x672 60.01d + 1024x576 59.95d 59.96d 59.90 59.82 + 960x600 59.93d 60.00d + 960x540 59.96d 59.99d 59.63 59.82 + 800x600 60.00d 60.32 56.25 + 840x525 60.01d 59.88d + 864x486 59.92 59.57 + 700x525 59.98d + 800x450 59.95d 59.82d + 640x512 60.02d + 700x450 59.96d 59.88d + 640x480 60.00d 59.94 + 720x405 59.51 58.99 + 684x384 59.88d 59.85d + 640x400 59.88d 59.98d + 640x360 59.86d 59.83d 59.84 59.32 + 512x384 60.00d + 512x288 60.00d 59.92d + 480x270 59.63d 59.82d + 400x300 60.32d 56.34d + 432x243 59.92d 59.57d + 320x240 60.05d + 360x202 59.51d 59.13d + 320x180 59.84d 59.32d +output DP-2 +output HDMI-2: primary 1920x1080+0+0 (531mm x 299mm) (fpr 0x43b791a2) + 1920x1080 60.00+* 50.00 59.94 60.00i 50.00i 59.94i + 1680x1050 59.88 + 1600x900 60.00 + 1280x1024 75.02 60.02 + 1280x800 59.91 + 1152x864 75.00 + 1280x720 60.00 50.00 59.94 + 1024x768 75.03 60.00 + 832x624 74.55 + 800x600 75.00 60.32 + 720x576 50.00 50.00i + 720x480 60.00 59.94 60.00i 59.94i + 640x480 75.00 60.00 59.94 + 720x400 70.08 +output DP-1 +monitor HDMI-2: active primary automatic 1920x1080+0+0 (531mm x 299mm) + output HDMI-2 diff --git a/prepare.sh b/prepare.sh new file mode 100755 index 0000000..237b806 --- /dev/null +++ b/prepare.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -o nounset -o pipefail -o errexit + +SUDO=${SUDO-} +DISTRO=${DISTRO-} +UPDATE=${UPDATE-} +while getopts "ud:sS:-" OPT; do + case $OPT in + d) DISTRO=$OPTARG ;; + u) UPDATE=1 ;; + s) SUDO=sudo ;; + S) SUDO=$OPTARG ;; + -) break ;; + ?) usage 2 ;; + esac +done +shift $((OPTIND-1)) + +if [ -z "$DISTRO" ]; then + if command -v lsb_release; then + DISTRO=$(lsb_release -is) + elif command -v pacman >/dev/null; then + DISTRO="Arch" + elif command -v apt-get >/dev/null; then + # TODO: debian + DISTRO="Ubuntu" + else + echo "unable to figure out distribution: $DISTRO" 1>&2 + exit 1 + fi +fi +echo "distro: $DISTRO" 1>&2 + +if [ "$DISTRO" = "Arch" ] || command -v pacman >/dev/null; then + if [ -n "$UPDATE" ]; then + $SUDO pacman -Sy 1>&2 + fi + $SUDO pacman -S --noconfirm 1>&2 \ + git \ + make gcc pkgconf \ + python gawk \ + lua libxrandr +elif [ "$DISTRO" = "Ubuntu" ] || command -v apt-get >/dev/null; then + if [ -n "$UPDATE" ]; then + $SUDO apt-get update 1>&2 + fi + $SUDO apt-get install --yes 1>&2 \ + --no-install-recommends --no-install-suggests \ + git ca-certificates \ + make gcc pkg-config \ + python3 gawk \ + liblua5.4-dev libxrandr-dev + echo "LUA_PKG=lua54" +else + echo "unconfigured distribution: $DISTRO" 1>&2 + exit 1 +fi diff --git a/tools/is-clean b/tools/is-clean new file mode 100755 index 0000000..d86219d --- /dev/null +++ b/tools/is-clean @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import argparse +import hashlib +import os +import subprocess +import sys +import tempfile + +whoami = "is-clean" +env_prefix = whoami.upper().replace("-", "_") + "_" +def env(var, default=None): + return os.environ.get(env_prefix + var, default) + +import logging +logger = logging.getLogger(whoami) + +def is_clean(path, make=False, root=None, target=None, show_diff=False): + logger.info(f"checking: {path}") + + digest = "sha256" + with open(path, "rb") as f: + current_digest = hashlib.new(digest, f.read()).hexdigest() + logger.info(f"current; {digest.upper()}: {current_digest}") + + if make: + root = root or os.path.dirname(path) or os.getcwd() + logger.debug(f"root: {root}") + + target = target or os.path.relpath(path, root) + logger.debug(f"target: {target}") + + cmdline = [ os.environ.get("MAKE", "make") ] + cmdline += [ "-C", root ] + cmdline += [ target ] + logger.debug(f"running: {cmdline}") + subprocess.check_call(cmdline) + + with open(path, "rb") as f: + generated_digest = hashlib.new(digest, f.read()).hexdigest() + logger.info(f"generated; {digest.upper()}: {generated_digest}") + current_digest = generated_digest + + with tempfile.TemporaryDirectory(prefix=f"{whoami}-") as tmp: + head = os.path.join(tmp, "head") + + with open(head, "xb") as f: + rel = os.path.relpath(path, start=root) + cmdline = ["git", "show", "HEAD:./" + rel] + logger.debug(f"running: {cmdline}") + bs = subprocess.check_output(cmdline, cwd=root) + head_digest = hashlib.new(digest, bs).hexdigest() + f.write(bs) + + logger.info(f"HEAD; {digest.upper()}: {head_digest}") + + if current_digest == head_digest: + return True + + if show_diff: + subprocess.run(["diff", path, head]) + + return False + +def setup_logger(level, logger=None): + level = level.upper() + if logger is None: + logger = logging.getLogger() + logger.setLevel(level) + + handler = logging.StreamHandler() + handler.setLevel(level) + logger.addHandler(handler) + + fmt = logging.Formatter(fmt="%(asctime)s:%(name)s:%(levelname)s %(message)s", datefmt="%Y-%m-%dT%H:%M:%S%z") + handler.setFormatter(fmt) + +def parse_args(): + parser = argparse.ArgumentParser( + description="Freshness checker", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + parser.add_argument("--log", default=env("LOG_LEVEL", "INFO"), help="set log level") + + parser.add_argument("-m", "--make", action="store_true") + parser.add_argument("-r", "--root", metavar="PATH") + parser.add_argument("-t", "--target", metavar="TARGET") + + parser.add_argument("-D", "--no-diff", action="store_true") + + parser.add_argument("path", metavar="PATH") + + return parser.parse_args() + +def main(): + args = parse_args() + setup_logger(args.log, logger) + logger.debug(f"args: {args}") + + r = is_clean( + path = args.path, + make = args.make, + root = args.root, + target = args.target, + show_diff = not args.no_diff, + ) + + if not r: + sys.exit(1) + +if __name__ == "__main__": + main()