mailarchive of the pengutronix oss-tools mailing list
 help / color / mirror / Atom feed
* [OSS-Tools] [PATCH platsch V6 1/5] platsch: constify draw_buffer
@ 2024-06-23 11:54 LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 2/5] convert to meson build LI Qingwu
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: LI Qingwu @ 2024-06-23 11:54 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

From: Marco Felsch <m.felsch@pengutronix.de>

Make the dir and base const.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 platsch.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/platsch.c b/platsch.c
index 535b589..1aaa8d5 100644
--- a/platsch.c
+++ b/platsch.c
@@ -111,7 +111,7 @@ struct modeset_dev {
 	uint32_t crtc_id;
 };
 
-void draw_buffer(struct modeset_dev *dev, char *dir, char *base)
+void draw_buffer(struct modeset_dev *dev, const char *dir, const char *base)
 {
 	int fd_src;
 	char filename[128];
-- 
2.34.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [OSS-Tools] [PATCH platsch V6 2/5] convert to meson build
  2024-06-23 11:54 [OSS-Tools] [PATCH platsch V6 1/5] platsch: constify draw_buffer LI Qingwu
@ 2024-06-23 11:54 ` LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 3/5] platsch: split into platsch and libplatsch LI Qingwu
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: LI Qingwu @ 2024-06-23 11:54 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

Convert to meson build and update the README.rst
version update to 2024.06.0
cleanup .gitignore

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 .gitignore   | 14 --------------
 Makefile.am  | 23 -----------------------
 README.rst   | 10 ++++++++++
 configure.ac | 13 -------------
 meson.build  | 12 ++++++++++++
 5 files changed, 22 insertions(+), 50 deletions(-)
 delete mode 100644 Makefile.am
 delete mode 100644 configure.ac
 create mode 100644 meson.build

diff --git a/.gitignore b/.gitignore
index bef7e68..e69de29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,14 +0,0 @@
-/*.o
-/aclocal.m4
-/autom4te.cache
-/compile
-/config.log
-/config.status
-/configure
-/depcomp
-/.deps
-/install-sh
-/Makefile
-/Makefile.in
-/missing
-/platsch
diff --git a/Makefile.am b/Makefile.am
deleted file mode 100644
index d149ae0..0000000
--- a/Makefile.am
+++ /dev/null
@@ -1,23 +0,0 @@
-EXTRA_DIST = README.rst LICENSE
-
-sbin_PROGRAMS = platsch
-
-platsch_SOURCES = platsch.c
-platsch_CFLAGS = $(LIBDRM_CFLAGS)
-platsch_LDADD = $(LIBDRM_LIBS)
-
-CLEANFILES = \
-        $(DIST_ARCHIVES)
-
-DISTCLEAN = \
-        config.log \
-        config.status \
-        Makefile
-
-MAINTAINERCLEANFILES = \
-	aclocal.m4 \
-	configure \
-	depcomp \
-	install-sh \
-	Makefile.in \
-	missing
diff --git a/README.rst b/README.rst
index e318120..e905437 100644
--- a/README.rst
+++ b/README.rst
@@ -141,3 +141,13 @@ By adding a Signed-off-by line (e.g. using ``git commit -s``) saying::
 
 (using your real name and e-mail address), you state that your contributions
 are in line with the DCO.
+
+Compiling Instructions
+----------------------
+
+.. code-block:: shell
+
+    meson setup  -Dprefer_static=true build
+    meson compile -C build
+
+To ensure fast startup, ``platsch`` prefers using static libraries.
diff --git a/configure.ac b/configure.ac
deleted file mode 100644
index 18878db..0000000
--- a/configure.ac
+++ /dev/null
@@ -1,13 +0,0 @@
-AC_PREREQ([2.69])
-AC_INIT([platsch], [2019.12.0], [oss-tools@pengutronix.de])
-AC_CONFIG_SRCDIR([platsch.c])
-AM_INIT_AUTOMAKE([foreign dist-xz])
-
-AC_PROG_CC
-AC_PROG_MAKE_SET
-
-PKG_CHECK_MODULES([LIBDRM], [libdrm >= 2.4.112])
-
-AC_CONFIG_FILES([Makefile])
-
-AC_OUTPUT
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..9060514
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,12 @@
+project('platsch', 'c', version: '2024.06.0', license: '0BSD')
+
+dep_libdrm = dependency('libdrm',
+    version: '>= 2.4.112'
+)
+
+executable('platsch',
+    'platsch.c',
+    dependencies: dep_libdrm,
+    install: true,
+    install_dir: 'sbin'
+)
-- 
2.34.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [OSS-Tools] [PATCH platsch V6 3/5] platsch: split into platsch and libplatsch
  2024-06-23 11:54 [OSS-Tools] [PATCH platsch V6 1/5] platsch: constify draw_buffer LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 2/5] convert to meson build LI Qingwu
@ 2024-06-23 11:54 ` LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 4/5] Platsch: always fork child process LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 5/5] Add spinner executable for boot animation and text show LI Qingwu
  3 siblings, 0 replies; 5+ messages in thread
From: LI Qingwu @ 2024-06-23 11:54 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 libplatsch.c | 557 ++++++++++++++++++++++++++++++++++++++++++++++++
 libplatsch.h |  43 ++++
 meson.build  |  12 +-
 platsch.c    | 582 ++-------------------------------------------------
 4 files changed, 629 insertions(+), 565 deletions(-)
 create mode 100644 libplatsch.c
 create mode 100644 libplatsch.h

diff --git a/libplatsch.c b/libplatsch.c
new file mode 100644
index 0000000..f002491
--- /dev/null
+++ b/libplatsch.c
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2019 Pengutronix, Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+ *
+ * Permission to use, copy, modify, and/or distribute this software
+ * for any purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
+ * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+ * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Some code parts base on example code written in 2012 by David Herrmann
+ * <dh.herrmann@googlemail.com> and dedicated to the Public Domain. It was found
+ * in 2019 on
+ * https://raw.githubusercontent.com/dvdhrm/docs/master/drm-howto/modeset.c
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+
+#include "libplatsch.h"
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))
+
+ssize_t readfull(int fd, void *buf, size_t count)
+{
+	ssize_t ret = 0, err;
+
+	while (count > 0) {
+		err = read(fd, buf, count);
+		if (err < 0)
+			return err;
+		else if (err > 0) {
+			buf += err;
+			count -= err;
+			ret += err;
+		} else {
+			return ret;
+		}
+	}
+
+	return ret;
+}
+
+void draw_buffer(struct modeset_dev *dev, const char *dir, const char *base)
+{
+	int fd_src;
+	char filename[PLATSCH_MAX_FILENAME_LENGTH];
+	ssize_t size;
+	int ret;
+
+	/*
+	 * make it easy and load a raw file in the right format instead of
+	 * opening an (say) PNG and convert the image data to the right format.
+	 */
+	ret = snprintf(filename, sizeof(filename), "%s/%s-%ux%u-%s.bin", dir, base, dev->width,
+		       dev->height, dev->format->name);
+	if (ret >= sizeof(filename)) {
+		platsch_error("Failed to fit filename into buffer\n");
+		return;
+	}
+
+	fd_src = open(filename, O_RDONLY | O_CLOEXEC);
+	if (fd_src < 0) {
+		platsch_error("Failed to open %s: %m\n", filename);
+		return;
+	}
+
+	size = readfull(fd_src, dev->map, dev->size);
+	if (size < dev->size) {
+		if (size < 0)
+			platsch_error("Failed to read from %s: %m\n", filename);
+		else
+			platsch_error("Could only read %zd/%u bytes from %s\n", size, dev->size, filename);
+	}
+
+	ret = close(fd_src);
+	if (ret < 0) {
+		/* Nothing we can do about this, so just warn */
+		platsch_error("Failed to close image file\n");
+	}
+
+	return;
+}
+
+static struct modeset_dev *modeset_list = NULL;
+
+static int drmprepare_crtc(int fd, drmModeRes *res, drmModeConnector *conn, struct modeset_dev *dev)
+{
+	drmModeEncoder *enc;
+	unsigned int i, j;
+	int32_t crtc_id;
+	struct modeset_dev *iter;
+
+	/* first try the currently connected encoder+crtc */
+	if (conn->encoder_id) {
+		platsch_debug("connector #%d uses encoder #%d\n", conn->connector_id, conn->encoder_id);
+		enc = drmModeGetEncoder(fd, conn->encoder_id);
+		assert(enc);
+		assert(enc->encoder_id == conn->encoder_id);
+	} else {
+		platsch_debug("connector #%d has no active encoder\n", conn->connector_id);
+		enc = NULL;
+		dev->setmode = 1;
+	}
+
+	if (enc) {
+		if (enc->crtc_id) {
+			crtc_id = enc->crtc_id;
+			assert(crtc_id >= 0);
+
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc_id == crtc_id) {
+					crtc_id = -1;
+					break;
+				}
+			}
+
+			if (crtc_id > 0) {
+				platsch_debug("encoder #%d uses crtc #%d\n", enc->encoder_id, enc->crtc_id);
+				drmModeFreeEncoder(enc);
+				dev->crtc_id = crtc_id;
+				return 0;
+			} else {
+				platsch_debug("encoder #%d used crtc #%d, but that's in use\n",
+				      enc->encoder_id, iter->crtc_id);
+			}
+		} else {
+			platsch_debug("encoder #%d doesn't have an active crtc\n", enc->encoder_id);
+		}
+
+		drmModeFreeEncoder(enc);
+	}
+
+	/* If the connector is not currently bound to an encoder or if the
+	 * encoder+crtc is already used by another connector (actually unlikely
+	 * but let's be safe), iterate all other available encoders to find a
+	 * matching CRTC. */
+	for (i = 0; i < conn->count_encoders; ++i) {
+		enc = drmModeGetEncoder(fd, conn->encoders[i]);
+		if (!enc) {
+			platsch_error("Cannot retrieve encoder %u: %m\n", conn->encoders[i]);
+			continue;
+		}
+		assert(enc->encoder_id == conn->encoders[i]);
+
+		/* iterate all global CRTCs */
+		for (j = 0; j < res->count_crtcs; ++j) {
+			/* check whether this CRTC works with the encoder */
+			if (!(enc->possible_crtcs & (1 << j)))
+				continue;
+
+			/* check that no other device already uses this CRTC */
+			crtc_id = res->crtcs[j];
+			for (iter = modeset_list; iter; iter = iter->next) {
+				if (iter->crtc_id == crtc_id) {
+					crtc_id = -1;
+					break;
+				}
+			}
+
+			/* we have found a CRTC, so save it and return */
+			if (crtc_id >= 0) {
+				platsch_debug("encoder #%d will use crtc #%d\n", enc->encoder_id, crtc_id);
+				drmModeFreeEncoder(enc);
+				dev->crtc_id = crtc_id;
+				return 0;
+			}
+		}
+		drmModeFreeEncoder(enc);
+	}
+
+	platsch_error("Cannot find suitable CRTC for connector #%u\n", conn->connector_id);
+	return -ENOENT;
+}
+
+static int modeset_create_fb(int fd, struct modeset_dev *dev)
+{
+	struct drm_mode_create_dumb creq;
+	struct drm_mode_destroy_dumb dreq;
+	struct drm_mode_map_dumb mreq;
+	int ret;
+
+	/* create dumb buffer */
+	memset(&creq, 0, sizeof(creq));
+	creq.width = dev->width;
+	creq.height = dev->height;
+	creq.bpp = dev->format->bpp;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
+	if (ret < 0) {
+		platsch_error("Cannot create dumb buffer: %m\n");
+		return -errno;
+	}
+	dev->stride = creq.pitch;
+	dev->size = creq.size;
+	dev->handle = creq.handle;
+
+	/* create framebuffer object for the dumb-buffer */
+	ret = drmModeAddFB2(fd, dev->width, dev->height,
+			    dev->format->format,
+			    (uint32_t[4]){ dev->handle, },
+			    (uint32_t[4]){ dev->stride, },
+			    (uint32_t[4]){ 0, },
+			    &dev->fb_id, 0);
+	if (ret) {
+		ret = -errno;
+		platsch_error("Cannot create framebuffer: %m\n");
+		goto err_destroy;
+	}
+
+	/* prepare buffer for memory mapping */
+	memset(&mreq, 0, sizeof(mreq));
+	mreq.handle = dev->handle;
+	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
+	if (ret) {
+		ret = -errno;
+		platsch_error("Cannot get mmap offset: %m\n");
+		goto err_fb;
+	}
+
+	/* perform actual memory mapping */
+	dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mreq.offset);
+	if (dev->map == MAP_FAILED) {
+		ret = -errno;
+		platsch_error("Cannot mmap dumb buffer: %m\n");
+		goto err_fb;
+	}
+
+	/*
+	 * Clear the framebuffer. Normally it's overwritten later with some
+	 * image data, but in case this fails, initialize to all-black.
+	 */
+	memset(dev->map, 0x0, dev->size);
+
+	return 0;
+
+err_fb:
+	drmModeRmFB(fd, dev->fb_id);
+err_destroy:
+	memset(&dreq, 0, sizeof(dreq));
+	dreq.handle = dev->handle;
+	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
+	return ret;
+}
+
+/* Returns lowercase connector type names with '_' for '-' */
+static char *get_normalized_conn_type_name(uint32_t connector_type)
+{
+	int i;
+	const char *connector_name;
+	char *normalized_name;
+
+	connector_name = drmModeGetConnectorTypeName(connector_type);
+	if (!connector_name)
+		return NULL;
+
+	normalized_name = strdup(connector_name);
+
+	for (i = 0; normalized_name[i]; i++) {
+		normalized_name[i] = tolower(normalized_name[i]);
+		if (normalized_name[i] == '-')
+			normalized_name[i] = '_';
+	}
+
+	return normalized_name;
+}
+
+static const struct platsch_format *platsch_format_find(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(platsch_formats); i++)
+		if (!strcmp(platsch_formats[i].name, name))
+			return &platsch_formats[i];
+
+	return NULL;
+}
+
+static int set_env_connector_mode(drmModeConnector *conn, struct modeset_dev *dev)
+{
+	int ret, i = 0;
+	u_int32_t width = 0, height = 0;
+	const char *mode;
+	char *connector_type_name, mode_env_name[32], fmt_specifier[32] = "";
+	const struct platsch_format *format = NULL;
+
+	connector_type_name = get_normalized_conn_type_name(conn->connector_type);
+	if (!connector_type_name) {
+		platsch_error("could not look up name for connector type %u\n", conn->connector_type);
+		goto fallback;
+	}
+
+	ret = snprintf(mode_env_name, sizeof(mode_env_name), "platsch_%s%u_mode",
+		       connector_type_name, conn->connector_type_id);
+	free(connector_type_name);
+	if (ret >= sizeof(mode_env_name)) {
+		platsch_error("failed to fit platsch env mode variable name into buffer\n");
+		return -EFAULT;
+	}
+
+	/* check for connector mode configuration in environment */
+	platsch_debug("looking up %s env variable\n", mode_env_name);
+	mode = getenv(mode_env_name);
+	if (!mode)
+		goto fallback;
+
+	/* format suffix is optional */
+	ret = sscanf(mode, "%ux%u@%s", &width, &height, fmt_specifier);
+	if (ret < 2) {
+		platsch_error("error while scanning %s for mode\n", mode_env_name);
+		return -EFAULT;
+	}
+
+	/* use first mode matching given resolution */
+	for (i = 0; i < conn->count_modes; i++) {
+		drmModeModeInfo mode = conn->modes[i];
+		if (mode.hdisplay == width && mode.vdisplay == height) {
+			memcpy(&dev->mode, &mode, sizeof(dev->mode));
+			dev->width = width;
+			dev->height = height;
+			break;
+		}
+	}
+
+	if (i == conn->count_modes) {
+		platsch_error("no mode available matching %ux%u\n", width, height);
+		return -ENOENT;
+	}
+
+	format = platsch_format_find(fmt_specifier);
+	if (!format) {
+		if (strlen(fmt_specifier))
+			platsch_error("unknown format specifier %s\n", fmt_specifier);
+		goto fallback_format;
+	}
+
+	dev->format = format;
+
+	return 0;
+
+fallback:
+	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
+	dev->width = conn->modes[0].hdisplay;
+	dev->height = conn->modes[0].vdisplay;
+	platsch_debug("using default mode for connector #%u\n", conn->connector_id);
+
+fallback_format:
+	dev->format = &platsch_formats[0];
+	platsch_debug("using default format %s for connector #%u\n", dev->format->name, conn->connector_id);
+
+	return 0;
+}
+
+static int drmprepare_connector(int fd, drmModeRes *res, drmModeConnector *conn,
+				struct modeset_dev *dev)
+{
+	int ret;
+
+	/* check if a monitor is connected */
+	if (conn->connection != DRM_MODE_CONNECTED) {
+		platsch_error("Ignoring unused connector #%u\n", conn->connector_id);
+		return -ENOENT;
+	}
+
+	/* check if there is at least one valid mode */
+	if (conn->count_modes == 0) {
+		platsch_error("no valid mode for connector #%u\n", conn->connector_id);
+		return -EFAULT;
+	}
+
+	/* configure mode information in our device structure */
+	ret = set_env_connector_mode(conn, dev);
+	if (ret) {
+		platsch_error("no valid mode for connector #%u\n", conn->connector_id);
+		return ret;
+	}
+	platsch_debug("mode for connector #%u is %ux%u@%s\n", conn->connector_id, dev->width, dev->height,
+	      dev->format->name);
+
+	/* find a crtc for this connector */
+	ret = drmprepare_crtc(fd, res, conn, dev);
+	if (ret) {
+		platsch_error("no valid crtc for connector #%u\n", conn->connector_id);
+		return ret;
+	}
+
+	/* create a framebuffer for this CRTC */
+	ret = modeset_create_fb(fd, dev);
+	if (ret) {
+		platsch_error("cannot create framebuffer for connector #%u\n", conn->connector_id);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int drmprepare(int fd)
+{
+	drmModeRes *res;
+	drmModeConnector *conn;
+	unsigned int i;
+	struct modeset_dev *dev;
+	int ret;
+
+	/* retrieve resources */
+	res = drmModeGetResources(fd);
+	if (!res) {
+		platsch_error("cannot retrieve DRM resources: %m\n");
+		return -errno;
+	}
+
+	platsch_debug("Found %d connectors\n", res->count_connectors);
+
+	/* iterate all connectors */
+	for (i = 0; i < res->count_connectors; ++i) {
+		/* get information for each connector */
+		conn = drmModeGetConnector(fd, res->connectors[i]);
+		if (!conn) {
+			platsch_error("Cannot retrieve DRM connector #%u: %m\n", res->connectors[i]);
+			continue;
+		}
+		assert(conn->connector_id == res->connectors[i]);
+
+		platsch_debug("Connector #%u has type %s\n", conn->connector_id,
+		      drmModeGetConnectorTypeName(conn->connector_type));
+
+		/* create a device structure */
+		dev = malloc(sizeof(*dev));
+		if (!dev) {
+			platsch_error("Cannot allocate memory for connector #%u: %m\n", res->connectors[i]);
+			continue;
+		}
+		memset(dev, 0, sizeof(*dev));
+		dev->conn_id = conn->connector_id;
+
+		ret = drmprepare_connector(fd, res, conn, dev);
+		if (ret) {
+			if (ret != -ENOENT) {
+				platsch_error("Cannot setup device for connector #%u: %m\n",
+				      res->connectors[i]);
+			}
+			free(dev);
+			drmModeFreeConnector(conn);
+			continue;
+		}
+
+		/* free connector data and link device into global list */
+		drmModeFreeConnector(conn);
+		dev->next = modeset_list;
+		modeset_list = dev;
+	}
+
+	/* free resources again */
+	drmModeFreeResources(res);
+	return 0;
+}
+
+static int drmfd;
+
+struct modeset_dev *platsch_init(void)
+{
+	char drmdev[PLATSCH_MAX_FILENAME_LENGTH];
+	int ret = 0, i;
+
+	for (i = 0; i < 64; i++) {
+		struct drm_mode_card_res res = {0};
+
+		/*
+		 * XXX: Maybe use drmOpen instead?
+		 * (Where should name/busid come from?)
+		 * XXX: Loop through drm devices to find one with connectors.
+		 */
+		ret = snprintf(drmdev, sizeof(drmdev), DRM_DEV_NAME, DRM_DIR_NAME, i);
+		if (ret >= sizeof(drmdev)) {
+			platsch_error("Huh, device name overflowed buffer\n");
+			goto fail;
+		}
+
+		drmfd = open(drmdev, O_RDWR | O_CLOEXEC, 0);
+		if (drmfd < 0) {
+			platsch_error("Failed to open drm device: %m\n");
+			goto fail;
+		}
+
+		ret = drmIoctl(drmfd, DRM_IOCTL_MODE_GETRESOURCES, &res);
+		if (ret < 0) {
+			close(drmfd);
+			continue;
+		} else {
+			/* Device found */
+			break;
+		}
+	}
+
+	if (i == 64) {
+		platsch_error("No suitable DRM device found\n");
+		goto fail;
+	}
+
+	ret = drmprepare(drmfd);
+	if (ret) {
+		platsch_error("Failed to prepare DRM device\n");
+		goto fail;
+	}
+
+	return modeset_list;
+
+fail:
+	return NULL;
+}
+
+void platsch_setup_display(struct modeset_dev *dev)
+{
+	int ret = 0;
+
+	if (dev->setmode) {
+		ret = drmModeSetCrtc(drmfd, dev->crtc_id, dev->fb_id, 0, 0, &dev->conn_id, 1,
+				     &dev->mode);
+		if (ret) {
+			platsch_error("Cannot set CRTC for connector #%u: %m\n", dev->conn_id);
+		}
+		/* don't need to set the mode again */
+		dev->setmode = 0;
+	} else {
+		ret = drmModePageFlip(drmfd, dev->crtc_id, dev->fb_id, 0, NULL);
+		if (ret) {
+			platsch_error("Page flip failed on connector #%u: %m\n", dev->conn_id);
+		}
+	}
+}
+
+void platsch_draw(struct modeset_dev *dev, const char *dir, const char *base)
+{
+	draw_buffer(dev, dir, base);
+	platsch_setup_display(dev);
+}
+
+void platsch_drmDropMaster(void)
+{
+	int ret = 0;
+
+	ret = drmDropMaster(drmfd);
+	if (ret)
+		platsch_error("Failed to drop master on drm device\n");
+
+}
diff --git a/libplatsch.h b/libplatsch.h
new file mode 100644
index 0000000..cf84c1d
--- /dev/null
+++ b/libplatsch.h
@@ -0,0 +1,43 @@
+#ifndef __LIBPLATSCH_H__
+#define __LIBPLATSCH_H__
+
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+#include <drm_fourcc.h>
+
+#define platsch_debug(fmt, ...) printf("%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
+#define platsch_error(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
+#define PLATSCH_MAX_FILENAME_LENGTH 128
+struct platsch_format {
+	uint32_t format;
+	uint32_t bpp;
+	const char *name;
+};
+struct modeset_dev {
+	struct modeset_dev *next;
+
+	uint32_t width;
+	uint32_t height;
+	uint32_t stride;
+	uint32_t size;
+	const struct platsch_format *format;
+	uint32_t handle;
+	void *map;
+	bool setmode;
+	drmModeModeInfo mode;
+	uint32_t fb_id;
+	uint32_t conn_id;
+	uint32_t crtc_id;
+};
+
+static const struct platsch_format platsch_formats[] = {
+	{ DRM_FORMAT_RGB565, 16, "RGB565" }, /* default */
+	{ DRM_FORMAT_XRGB8888, 32, "XRGB8888" },
+};
+
+struct modeset_dev *platsch_init(void);
+void platsch_draw(struct modeset_dev *dev, const char *dir, const char *base);
+void platsch_drmDropMaster(void);
+void platsch_setup_display(struct modeset_dev *dev);
+
+#endif
diff --git a/meson.build b/meson.build
index 9060514..ccc3d2d 100644
--- a/meson.build
+++ b/meson.build
@@ -4,8 +4,18 @@ dep_libdrm = dependency('libdrm',
     version: '>= 2.4.112'
 )
 
+libplasch_sources = ['libplatsch.c']
+
+libplatsch = static_library('platsch',
+    libplasch_sources,
+    dependencies: dep_libdrm
+)
+
+platsch_sources = ['platsch.c']
+
 executable('platsch',
-    'platsch.c',
+    platsch_sources,
+    link_with: libplatsch,
     dependencies: dep_libdrm,
     install: true,
     install_dir: 'sbin'
diff --git a/platsch.c b/platsch.c
index 1aaa8d5..6503066 100644
--- a/platsch.c
+++ b/platsch.c
@@ -19,48 +19,22 @@
  * https://raw.githubusercontent.com/dvdhrm/docs/master/drm-howto/modeset.c
  */
 
-#include <assert.h>
-#include <ctype.h>
-#include <errno.h>
+#include <fcntl.h>
 #include <getopt.h>
 #include <libgen.h>
-#include <stdarg.h>
-#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <fcntl.h>
-
-#include <xf86drm.h>
-#include <xf86drmMode.h>
-#include <drm_fourcc.h>
-
-#define debug(fmt, ...) printf("%s:%d: " fmt, __func__, __LINE__, ##__VA_ARGS__)
-#define error(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
-
-#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a))
 
-struct platsch_format {
-	uint32_t format;
-	uint32_t bpp;
-	const char *name;
-};
-
-static const struct platsch_format platsch_formats[] = {
-	{ DRM_FORMAT_RGB565, 16, "RGB565" }, /* default */
-	{ DRM_FORMAT_XRGB8888, 32, "XRGB8888" },
-};
+#include "libplatsch.h"
 
-void redirect_stdfd(void)
+void platsch_redirect_stdfd(void)
 {
 	int devnull = open("/dev/null", O_RDWR, 0);
 
 	if (devnull < 0) {
-		error("Failed to open /dev/null: %m\n");
+		platsch_error("Failed to open /dev/null: %m\n");
 		return;
 	}
 
@@ -73,475 +47,6 @@ void redirect_stdfd(void)
 	close(devnull);
 }
 
-ssize_t readfull(int fd, void *buf, size_t count)
-{
-	ssize_t ret = 0, err;
-
-	while (count > 0) {
-		err = read(fd, buf, count);
-		if (err < 0)
-			return err;
-		else if (err > 0) {
-			buf += err;
-			count -= err;
-			ret += err;
-		} else {
-			return ret;
-		}
-	}
-
-	return ret;
-}
-
-struct modeset_dev {
-	struct modeset_dev *next;
-
-	uint32_t width;
-	uint32_t height;
-	uint32_t stride;
-	uint32_t size;
-	const struct platsch_format *format;
-	uint32_t handle;
-	void *map;
-
-	bool setmode;
-	drmModeModeInfo mode;
-	uint32_t fb_id;
-	uint32_t conn_id;
-	uint32_t crtc_id;
-};
-
-void draw_buffer(struct modeset_dev *dev, const char *dir, const char *base)
-{
-	int fd_src;
-	char filename[128];
-	ssize_t size;
-	int ret;
-
-	/*
-	 * make it easy and load a raw file in the right format instead of
-	 * opening an (say) PNG and convert the image data to the right format.
-	 */
-	ret = snprintf(filename, sizeof(filename),
-		       "%s/%s-%ux%u-%s.bin",
-		       dir, base, dev->width, dev->height, dev->format->name);
-	if (ret >= sizeof(filename)) {
-		error("Failed to fit filename into buffer\n");
-		return;
-	}
-
-	fd_src = open(filename, O_RDONLY | O_CLOEXEC);
-	if (fd_src < 0) {
-		error("Failed to open %s: %m\n", filename);
-		return;
-	}
-
-	size = readfull(fd_src, dev->map, dev->size);
-	if (size < dev->size) {
-		if (size < 0)
-			error("Failed to read from %s: %m\n", filename);
-		else
-			error("Could only read %zd/%u bytes from %s\n",
-			      size, dev->size, filename);
-	}
-
-	ret = close(fd_src);
-	if (ret < 0) {
-		/* Nothing we can do about this, so just warn */
-		error("Failed to close image file\n");
-	}
-
-	return;
-}
-
-static struct modeset_dev *modeset_list = NULL;
-
-static int drmprepare_crtc(int fd, drmModeRes *res, drmModeConnector *conn,
-			   struct modeset_dev *dev)
-{
-	drmModeEncoder *enc;
-	unsigned int i, j;
-	int32_t crtc_id;
-	struct modeset_dev *iter;
-
-	/* first try the currently connected encoder+crtc */
-	if (conn->encoder_id) {
-		debug("connector #%d uses encoder #%d\n", conn->connector_id,
-		      conn->encoder_id);
-		enc = drmModeGetEncoder(fd, conn->encoder_id);
-		assert(enc);
-		assert(enc->encoder_id == conn->encoder_id);
-	} else {
-		debug("connector #%d has no active encoder\n",
-		      conn->connector_id);
-		enc = NULL;
-		dev->setmode = 1;
-	}
-
-	if (enc) {
-		if (enc->crtc_id) {
-			crtc_id = enc->crtc_id;
-			assert(crtc_id >= 0);
-
-			for (iter = modeset_list; iter; iter = iter->next) {
-				if (iter->crtc_id == crtc_id) {
-					crtc_id = -1;
-					break;
-				}
-			}
-
-			if (crtc_id > 0) {
-				debug("encoder #%d uses crtc #%d\n",
-				      enc->encoder_id, enc->crtc_id);
-				drmModeFreeEncoder(enc);
-				dev->crtc_id = crtc_id;
-				return 0;
-			} else {
-				debug("encoder #%d used crtc #%d, but that's in use\n",
-				      enc->encoder_id, iter->crtc_id);
-			}
-		} else {
-			debug("encoder #%d doesn't have an active crtc\n",
-			      enc->encoder_id);
-		}
-
-		drmModeFreeEncoder(enc);
-	}
-
-	/* If the connector is not currently bound to an encoder or if the
-	 * encoder+crtc is already used by another connector (actually unlikely
-	 * but let's be safe), iterate all other available encoders to find a
-	 * matching CRTC. */
-	for (i = 0; i < conn->count_encoders; ++i) {
-		enc = drmModeGetEncoder(fd, conn->encoders[i]);
-		if (!enc) {
-			error("Cannot retrieve encoder %u: %m\n",
-			      conn->encoders[i]);
-			continue;
-		}
-		assert(enc->encoder_id == conn->encoders[i]);
-
-		/* iterate all global CRTCs */
-		for (j = 0; j < res->count_crtcs; ++j) {
-			/* check whether this CRTC works with the encoder */
-			if (!(enc->possible_crtcs & (1 << j)))
-				continue;
-
-			/* check that no other device already uses this CRTC */
-			crtc_id = res->crtcs[j];
-			for (iter = modeset_list; iter; iter = iter->next) {
-				if (iter->crtc_id == crtc_id) {
-					crtc_id = -1;
-					break;
-				}
-			}
-
-			/* we have found a CRTC, so save it and return */
-			if (crtc_id >= 0) {
-				debug("encoder #%d will use crtc #%d\n",
-				      enc->encoder_id, crtc_id);
-				drmModeFreeEncoder(enc);
-				dev->crtc_id = crtc_id;
-				return 0;
-			}
-
-		}
-		drmModeFreeEncoder(enc);
-	}
-
-	error("Cannot find suitable CRTC for connector #%u\n",
-	      conn->connector_id);
-	return -ENOENT;
-}
-
-static int modeset_create_fb(int fd, struct modeset_dev *dev)
-{
-	struct drm_mode_create_dumb creq;
-	struct drm_mode_destroy_dumb dreq;
-	struct drm_mode_map_dumb mreq;
-	int ret;
-
-	/* create dumb buffer */
-	memset(&creq, 0, sizeof(creq));
-	creq.width = dev->width;
-	creq.height = dev->height;
-	creq.bpp = dev->format->bpp;
-	ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq);
-	if (ret < 0) {
-		error("Cannot create dumb buffer: %m\n");
-		return -errno;
-	}
-	dev->stride = creq.pitch;
-	dev->size = creq.size;
-	dev->handle = creq.handle;
-
-	/* create framebuffer object for the dumb-buffer */
-	ret = drmModeAddFB2(fd, dev->width, dev->height,
-			    dev->format->format,
-			    (uint32_t[4]){ dev->handle, },
-			    (uint32_t[4]){ dev->stride, },
-			    (uint32_t[4]){ 0, },
-			    &dev->fb_id, 0);
-	if (ret) {
-		ret = -errno;
-		error("Cannot create framebuffer: %m\n");
-		goto err_destroy;
-	}
-
-	/* prepare buffer for memory mapping */
-	memset(&mreq, 0, sizeof(mreq));
-	mreq.handle = dev->handle;
-	ret = drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq);
-	if (ret) {
-		ret = -errno;
-		error("Cannot get mmap offset: %m\n");
-		goto err_fb;
-	}
-
-	/* perform actual memory mapping */
-	dev->map = mmap(0, dev->size, PROT_READ | PROT_WRITE, MAP_SHARED,
-			fd, mreq.offset);
-	if (dev->map == MAP_FAILED) {
-		ret = -errno;
-		error("Cannot mmap dumb buffer: %m\n");
-		goto err_fb;
-	}
-
-	/*
-	 * Clear the framebuffer. Normally it's overwritten later with some
-	 * image data, but in case this fails, initialize to all-black.
-	 */
-	memset(dev->map, 0x0, dev->size);
-
-	return 0;
-
-err_fb:
-	drmModeRmFB(fd, dev->fb_id);
-err_destroy:
-	memset(&dreq, 0, sizeof(dreq));
-	dreq.handle = dev->handle;
-	drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq);
-	return ret;
-}
-
-/* Returns lowercase connector type names with '_' for '-' */
-static char *get_normalized_conn_type_name(uint32_t connector_type)
-{
-	int i;
-	const char *connector_name;
-	char *normalized_name;
-
-	connector_name = drmModeGetConnectorTypeName(connector_type);
-	if (!connector_name)
-		return NULL;
-
-	normalized_name = strdup(connector_name);
-
-	for (i = 0; normalized_name[i]; i++) {
-		normalized_name[i] = tolower(normalized_name[i]);
-		if (normalized_name[i] == '-')
-			normalized_name[i] = '_';
-	}
-
-	return normalized_name;
-}
-
-static const struct platsch_format *platsch_format_find(const char *name)
-{
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(platsch_formats); i++)
-		if (!strcmp(platsch_formats[i].name, name))
-			return &platsch_formats[i];
-
-	return NULL;
-}
-
-static int set_env_connector_mode(drmModeConnector *conn,
-				  struct modeset_dev *dev)
-{
-	int ret, i = 0;
-	u_int32_t width = 0, height = 0;
-	const char *mode;
-	char *connector_type_name, mode_env_name[32], fmt_specifier[32] = "";
-	const struct platsch_format *format = NULL;
-
-	connector_type_name = get_normalized_conn_type_name(conn->connector_type);
-	if (!connector_type_name) {
-		error("could not look up name for connector type %u\n",
-		      conn->connector_type);
-		goto fallback;
-	}
-
-	ret = snprintf(mode_env_name, sizeof(mode_env_name), "platsch_%s%u_mode",
-		       connector_type_name, conn->connector_type_id);
-	free(connector_type_name);
-	if (ret >= sizeof(mode_env_name)) {
-		error("failed to fit platsch env mode variable name into buffer\n");
-		return -EFAULT;
-	}
-
-	/* check for connector mode configuration in environment */
-	debug("looking up %s env variable\n", mode_env_name);
-	mode = getenv(mode_env_name);
-	if (!mode)
-		goto fallback;
-
-	/* format suffix is optional */
-	ret = sscanf(mode, "%ux%u@%s", &width, &height, fmt_specifier);
-	if (ret < 2) {
-		error("error while scanning %s for mode\n", mode_env_name);
-		return -EFAULT;
-	}
-
-	/* use first mode matching given resolution */
-	for (i = 0; i < conn->count_modes; i++) {
-		drmModeModeInfo mode = conn->modes[i];
-		if (mode.hdisplay == width && mode.vdisplay == height) {
-			memcpy(&dev->mode, &mode, sizeof(dev->mode));
-			dev->width = width;
-			dev->height = height;
-			break;
-		}
-	}
-
-	if (i == conn->count_modes) {
-		error("no mode available matching %ux%u\n", width, height);
-		return -ENOENT;
-	}
-
-	format = platsch_format_find(fmt_specifier);
-	if (!format) {
-		if (strlen(fmt_specifier))
-			error("unknown format specifier %s\n", fmt_specifier);
-		goto fallback_format;
-	}
-
-	dev->format = format;
-
-	return 0;
-
-fallback:
-	memcpy(&dev->mode, &conn->modes[0], sizeof(dev->mode));
-	dev->width = conn->modes[0].hdisplay;
-	dev->height = conn->modes[0].vdisplay;
-	debug("using default mode for connector #%u\n", conn->connector_id);
-
-fallback_format:
-	dev->format = &platsch_formats[0];
-	debug("using default format %s for connector #%u\n", dev->format->name,
-	      conn->connector_id);
-
-	return 0;
-}
-
-static int drmprepare_connector(int fd, drmModeRes *res, drmModeConnector *conn,
-				struct modeset_dev *dev)
-{
-	int ret;
-
-	/* check if a monitor is connected */
-	if (conn->connection != DRM_MODE_CONNECTED) {
-		error("Ignoring unused connector #%u\n", conn->connector_id);
-		return -ENOENT;
-	}
-
-	/* check if there is at least one valid mode */
-	if (conn->count_modes == 0) {
-		error("no valid mode for connector #%u\n", conn->connector_id);
-		return -EFAULT;
-	}
-
-	/* configure mode information in our device structure */
-	ret = set_env_connector_mode(conn, dev);
-	if (ret) {
-		error("no valid mode for connector #%u\n", conn->connector_id);
-		return ret;
-	}
-	debug("mode for connector #%u is %ux%u@%s\n",
-	      conn->connector_id, dev->width, dev->height, dev->format->name);
-
-	/* find a crtc for this connector */
-	ret = drmprepare_crtc(fd, res, conn, dev);
-	if (ret) {
-		error("no valid crtc for connector #%u\n", conn->connector_id);
-		return ret;
-	}
-
-	/* create a framebuffer for this CRTC */
-	ret = modeset_create_fb(fd, dev);
-	if (ret) {
-		error("cannot create framebuffer for connector #%u\n",
-		      conn->connector_id);
-		return ret;
-	}
-
-	return 0;
-}
-
-static int drmprepare(int fd)
-{
-	drmModeRes *res;
-	drmModeConnector *conn;
-	unsigned int i;
-	struct modeset_dev *dev;
-	int ret;
-
-	/* retrieve resources */
-	res = drmModeGetResources(fd);
-	if (!res) {
-		error("cannot retrieve DRM resources: %m\n");
-		return -errno;
-	}
-
-	debug("Found %d connectors\n", res->count_connectors);
-
-	/* iterate all connectors */
-	for (i = 0; i < res->count_connectors; ++i) {
-		/* get information for each connector */
-		conn = drmModeGetConnector(fd, res->connectors[i]);
-		if (!conn) {
-			error("Cannot retrieve DRM connector #%u: %m\n",
-				res->connectors[i]);
-			continue;
-		}
-		assert(conn->connector_id == res->connectors[i]);
-
-		debug("Connector #%u has type %s\n", conn->connector_id,
-		      drmModeGetConnectorTypeName(conn->connector_type));
-
-		/* create a device structure */
-		dev = malloc(sizeof(*dev));
-		if (!dev) {
-			error("Cannot allocate memory for connector #%u: %m\n",
-			      res->connectors[i]);
-			continue;
-		}
-		memset(dev, 0, sizeof(*dev));
-		dev->conn_id = conn->connector_id;
-
-		ret = drmprepare_connector(fd, res, conn, dev);
-		if (ret) {
-			if (ret != -ENOENT) {
-				error("Cannot setup device for connector #%u: %m\n",
-				      res->connectors[i]);
-			}
-			free(dev);
-			drmModeFreeConnector(conn);
-			continue;
-		}
-
-		/* free connector data and link device into global list */
-		drmModeFreeConnector(conn);
-		dev->next = modeset_list;
-		modeset_list = dev;
-	}
-
-	/* free resources again */
-	drmModeFreeResources(res);
-	return 0;
-}
-
 static struct option longopts[] =
 {
 	{ "help",      no_argument,       0, 'h' },
@@ -552,7 +57,7 @@ static struct option longopts[] =
 
 static void usage(const char *prog)
 {
-	error("Usage:\n"
+	platsch_error("Usage:\n"
 	      "%s [-d|--directory <dir>] [-b|--basename <name>]\n"
 	      "   [-h|--help]\n",
 	      prog);
@@ -561,14 +66,12 @@ static void usage(const char *prog)
 int main(int argc, char *argv[])
 {
 	char **initsargv;
-	int drmfd;
-	char drmdev[128];
 	struct modeset_dev *iter;
 	bool pid1 = getpid() == 1;
 	const char *dir = "/usr/share/platsch";
 	const char *base = "splash";
 	const char *env;
-	int ret = 0, c, i;
+	int ret = 0, c;
 
 	env = getenv("platsch_directory");
 	if (env)
@@ -580,7 +83,7 @@ int main(int argc, char *argv[])
 
 	if (!pid1) {
 		while ((c = getopt_long(argc, argv, "hd:b:", longopts, NULL)) != EOF) {
-			switch(c) {
+			switch (c) {
 			case 'd':
 				dir = optarg;
 				break;
@@ -598,77 +101,28 @@ int main(int argc, char *argv[])
 		}
 
 		if (optind < argc) {
-			error("Too many arguments!\n");
+			platsch_error("Too many arguments!\n");
 			usage(basename(argv[0]));
 			exit(1);
 		}
 	}
 
-	for (i = 0; i < 64; i++) {
-		struct drm_mode_card_res res = {0};
-
-		/*
-		 * XXX: Maybe use drmOpen instead?
-		 * (Where should name/busid come from?)
-		 * XXX: Loop through drm devices to find one with connectors.
-		 */
-		ret = snprintf(drmdev, sizeof(drmdev), DRM_DEV_NAME, DRM_DIR_NAME, i);
-		if (ret >= sizeof(drmdev)) {
-			error("Huh, device name overflowed buffer\n");
-			goto execinit;
-		}
-
-		drmfd = open(drmdev, O_RDWR | O_CLOEXEC, 0);
-		if (drmfd < 0) {
-			error("Failed to open drm device: %m\n");
-			goto execinit;
-		}
-
-		ret = drmIoctl(drmfd, DRM_IOCTL_MODE_GETRESOURCES, &res);
-		if (ret < 0) {
-			close(drmfd);
-			continue;
-		} else {
-			/* Device found */
-			break;
-		}
+	struct modeset_dev *modeset_list = platsch_init();
+	if (!modeset_list) {
+		platsch_error("Failed to initialize modeset\n");
+		return EXIT_FAILURE;
 	}
 
-	ret = drmprepare(drmfd);
-	assert(!ret);
-
 	for (iter = modeset_list; iter; iter = iter->next) {
-
-		/* draw first then set the mode */
-		draw_buffer(iter, dir, base);
-
-		if (iter->setmode) {
-			debug("set crtc\n");
-
-			ret = drmModeSetCrtc(drmfd, iter->crtc_id, iter->fb_id,
-					     0, 0, &iter->conn_id, 1, &iter->mode);
-			if (ret)
-				error("Cannot set CRTC for connector #%u: %m\n",
-				      iter->conn_id);
-		} else {
-			debug("page flip\n");
-			ret = drmModePageFlip(drmfd, iter->crtc_id, iter->fb_id,
-					      0, NULL);
-			if (ret)
-				error("Page flip failed on connector #%u: %m\n",
-				      iter->conn_id);
-		}
+		platsch_draw(iter, dir, base);
 	}
 
-	ret = drmDropMaster(drmfd);
-	if (ret)
-		error("Failed to drop master on drm device\n");
+	platsch_drmDropMaster();
 
-execinit:
 	if (pid1) {
 		ret = fork();
 		if (ret < 0) {
-			error("failed to fork for init: %m\n");
+			platsch_error("failed to fork for init: %m\n");
 		} else if (ret == 0) {
 			/*
 			 * in the child go to sleep to keep the drm device open
@@ -679,7 +133,7 @@ execinit:
 
 		initsargv = calloc(sizeof(argv[0]), argc + 1);
 		if (!initsargv) {
-			error("failed to allocate argv for init\n");
+			platsch_error("failed to allocate argv for init\n");
 			return EXIT_FAILURE;
 		}
 		memcpy(initsargv, argv, argc * sizeof(argv[0]));
@@ -688,13 +142,13 @@ execinit:
 
 		execv("/sbin/init", initsargv);
 
-		error("failed to exec init: %m\n");
+		platsch_error("failed to exec init: %m\n");
 
 		return EXIT_FAILURE;
 	}
 
 sleep:
-	redirect_stdfd();
+	platsch_redirect_stdfd();
 
 	do {
 		sleep(10);
-- 
2.34.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [OSS-Tools] [PATCH platsch V6 4/5] Platsch: always fork child process
  2024-06-23 11:54 [OSS-Tools] [PATCH platsch V6 1/5] platsch: constify draw_buffer LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 2/5] convert to meson build LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 3/5] platsch: split into platsch and libplatsch LI Qingwu
@ 2024-06-23 11:54 ` LI Qingwu
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 5/5] Add spinner executable for boot animation and text show LI Qingwu
  3 siblings, 0 replies; 5+ messages in thread
From: LI Qingwu @ 2024-06-23 11:54 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

When running Platsch in the background with an ampersand,
the initramfs may disappear after the rootfs is mounted.
In such cases, Platsch may fail to read the resources.
To address this, Platsch now always forks a child process
after acquiring the resources.
The chiled process keeps the DRM devices remain open
while the parent process exits with a clear status.

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 platsch.c | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/platsch.c b/platsch.c
index 6503066..756ba1c 100644
--- a/platsch.c
+++ b/platsch.c
@@ -119,17 +119,18 @@ int main(int argc, char *argv[])
 
 	platsch_drmDropMaster();
 
+	ret = fork();
+	if (ret < 0) {
+		platsch_error("failed to fork for init: %m\n");
+	} else if (ret == 0) {
+		/*
+		* in the child go to sleep to keep the drm device open
+		* and give pid 1 to init.
+		*/
+		goto sleep;
+	}
+
 	if (pid1) {
-		ret = fork();
-		if (ret < 0) {
-			platsch_error("failed to fork for init: %m\n");
-		} else if (ret == 0) {
-			/*
-			 * in the child go to sleep to keep the drm device open
-			 * and give pid 1 to init.
-			 */
-			goto sleep;
-		}
 
 		initsargv = calloc(sizeof(argv[0]), argc + 1);
 		if (!initsargv) {
@@ -147,6 +148,8 @@ int main(int argc, char *argv[])
 		return EXIT_FAILURE;
 	}
 
+	return EXIT_SUCCESS;
+
 sleep:
 	platsch_redirect_stdfd();
 
-- 
2.34.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [OSS-Tools] [PATCH platsch V6 5/5] Add spinner executable for boot animation and text show
  2024-06-23 11:54 [OSS-Tools] [PATCH platsch V6 1/5] platsch: constify draw_buffer LI Qingwu
                   ` (2 preceding siblings ...)
  2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 4/5] Platsch: always fork child process LI Qingwu
@ 2024-06-23 11:54 ` LI Qingwu
  3 siblings, 0 replies; 5+ messages in thread
From: LI Qingwu @ 2024-06-23 11:54 UTC (permalink / raw)
  To: Qing-wu.Li, oss-tools, m.felsch; +Cc: bsp-development.geo

This commit introduces a new executable, spinner,
    which supports two types of animations for boot sequences:
    1 static PNG and text support.
    2 rotates square PNG images per frame
    3 shows a sequence of square images from a strip of PNG images.

Signed-off-by: LI Qingwu <Qing-wu.Li@leica-geosystems.com.cn>
---
 README.rst        |  49 +++++++
 meson.build       |  20 +++
 meson_options.txt |   1 +
 spinner.c         | 323 ++++++++++++++++++++++++++++++++++++++++++++++
 spinner_conf.c    |  73 +++++++++++
 spinner_conf.h    |  39 ++++++
 6 files changed, 505 insertions(+)
 create mode 100644 meson_options.txt
 create mode 100644 spinner.c
 create mode 100644 spinner_conf.c
 create mode 100644 spinner_conf.h

diff --git a/README.rst b/README.rst
index e905437..27765ac 100644
--- a/README.rst
+++ b/README.rst
@@ -151,3 +151,52 @@ Compiling Instructions
     meson compile -C build
 
 To ensure fast startup, ``platsch`` prefers using static libraries.
+
+Spinner - Splash Screen with Animation
+======================================
+
+The ``spinner`` executable is designed to provide boot animations. It supports
+three types of animations:
+
+1. **Static PNG and Text Support**: Displays a static PNG image and text.
+2. **Square PNG Rotation Animation**: Rotates a square PNG image.
+3. **Sequence Move Rectangle Animation**: Displays a sequence of square images
+   from a strip of PNG images.
+
+To build the ``spinner`` executable, use the following commands:
+
+.. code-block:: shell
+
+    meson setup -Dspinner=true -Dprefer_static=true build
+    meson compile -C build
+
+Spinner Configuration
+---------------------
+
+The configuration for the ``spinner`` executable is read from a configuration
+file, with a default path of ``/usr/share/platsch/spinner.conf``. The directory
+of the configuration file can be set via the ``platsch_directory`` environment
+variable.
+
+Here is a sample configuration file (``spinner.conf``):
+
+.. code-block:: ini
+
+   symbol="/usr/share/platsch/Spinner.png"
+   fps=20
+   text=""
+   text_x=350
+   text_y=400
+   text_font="Sans"
+   text_size=30
+   text=qwqewrqr
+   symbol_x=-1
+   symbol_y=-1
+   rotate_step=0.1
+
+- Set ``text`` to empty if you don't want to display any text.
+- Set ``symbol`` to empty if you don't want to display any animation.
+- The background image is indicated by ``--directory`` and ``--basename``.
+- Set ``symbol_x`` and ``symbol_y`` to negative values for automatic centering.
+
+Please stop the spinner process after the system has booted, otherwise it keeps causing unnecessary CPU load.
diff --git a/meson.build b/meson.build
index ccc3d2d..c9c0e76 100644
--- a/meson.build
+++ b/meson.build
@@ -20,3 +20,23 @@ executable('platsch',
     install: true,
     install_dir: 'sbin'
 )
+
+if get_option('spinner')
+    spinner_dep = [
+        dependency('cairo', version: '>= 1.0'),
+        dep_libdrm
+    ]
+
+    spinner_src = [
+        'spinner.c',
+        'spinner_conf.c'
+    ]
+
+    executable('spinner',
+        spinner_src,
+        dependencies: spinner_dep,
+        link_with: libplatsch,
+        install: true,
+        install_dir:'sbin'
+    )
+endif
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..607ecaf
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1 @@
+option('spinner', type: 'boolean', value: false, description: 'Enable spinner build')
diff --git a/spinner.c b/spinner.c
new file mode 100644
index 0000000..dc4a191
--- /dev/null
+++ b/spinner.c
@@ -0,0 +1,323 @@
+
+#include <cairo.h>
+#include <math.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "libplatsch.h"
+#include "spinner_conf.h"
+
+typedef struct spinner {
+	cairo_format_t format;
+	cairo_surface_t *background_surface;
+	cairo_surface_t *symbol_surface;
+	cairo_t *disp_cr;
+	int display_height;
+	int display_width;
+	int symbol_frames;
+	int symbol_height;
+	int symbol_width;
+	int symbol_x;
+	int symbol_y;
+	float rotate_step;
+	struct modeset_dev *dev;
+	struct spinner *next;
+} spinner_t;
+
+void draw_background(cairo_t *cr, spinner_t *data)
+{
+	cairo_set_source_surface(cr, data->background_surface, -data->symbol_x, -data->symbol_y);
+	cairo_paint(cr);
+	cairo_save(cr);
+}
+
+void on_draw_sequence_animation(cairo_t *cr, spinner_t *data)
+{
+	static int frame;
+
+	int x = -data->symbol_width / 2;
+	int y = -data->symbol_height / 2;
+
+	int width = cairo_image_surface_get_width(data->symbol_surface);
+	int height = cairo_image_surface_get_height(data->symbol_surface);
+
+	if (width > height)
+		x = -data->symbol_width / 2 - frame * data->symbol_width;
+	else
+		y = -data->symbol_height / 2 - frame * data->symbol_height;
+
+	draw_background(cr, data);
+	cairo_translate(cr, data->symbol_width / 2, data->symbol_height / 2);
+	cairo_set_source_surface(cr, data->symbol_surface, x, y);
+	cairo_paint(cr);
+	cairo_restore(cr);
+	frame = (frame + 1) % data->symbol_frames;
+}
+
+void on_draw_rotation_animation(cairo_t *cr, spinner_t *data)
+{
+	static float angle = 0.0;
+
+	draw_background(cr, data);
+	cairo_translate(cr, data->symbol_width / 2, data->symbol_height / 2);
+	cairo_rotate(cr, angle);
+	cairo_set_source_surface(cr, data->symbol_surface, -data->symbol_width / 2,
+				 -data->symbol_height / 2);
+	cairo_paint(cr);
+	cairo_restore(cr);
+	angle += data->rotate_step;
+	if (angle > 2 * M_PI)
+		angle = 0.0;
+}
+
+static uint32_t convert_to_cairo_format(uint32_t format)
+{
+	switch (format) {
+	case DRM_FORMAT_RGB565:
+		return CAIRO_FORMAT_RGB16_565;
+	case DRM_FORMAT_XRGB8888:
+		return CAIRO_FORMAT_ARGB32;
+	}
+	return CAIRO_FORMAT_INVALID;
+}
+
+static void cairo_draw_text(cairo_t *cr, Config config)
+{
+	if (strlen(config.text) > 0) {
+		cairo_select_font_face(cr, config.text_font, CAIRO_FONT_SLANT_NORMAL,
+				       CAIRO_FONT_WEIGHT_NORMAL);
+		cairo_set_font_size(cr, (double)config.text_size);
+		cairo_move_to(cr, config.text_x, config.text_y);
+		cairo_set_source_rgb(cr, 0, 0, 0);
+		cairo_show_text(cr, config.text);
+	}
+}
+
+static int cairo_create_display_surface(struct modeset_dev *dev, spinner_t *data)
+{
+	cairo_surface_t *disp_surface = cairo_image_surface_create_for_data(
+	    dev->map, convert_to_cairo_format(dev->format->format), dev->width, dev->height,
+	    dev->stride);
+	if (cairo_surface_status(disp_surface)) {
+		platsch_error("Failed to create cairo surface\n");
+		return EXIT_FAILURE;
+	}
+	data->display_width = cairo_image_surface_get_width(disp_surface);
+	data->display_height = cairo_image_surface_get_height(disp_surface);
+	data->format = cairo_image_surface_get_format(disp_surface);
+	data->disp_cr = cairo_create(disp_surface);
+	return EXIT_SUCCESS;
+}
+
+// create a surface and paint background image
+static int cairo_create_background_surface(spinner_t *data, Config config, const char *dir,
+					   const char *base)
+{
+	char file_name[PLATSCH_MAX_FILENAME_LENGTH];
+	int ret;
+
+	ret = snprintf(file_name, sizeof(file_name), "%s/%s.png", dir, base);
+	if (ret >= sizeof(file_name)) {
+		platsch_error("Failed to fit file_name into buffer\n");
+		return EXIT_FAILURE;
+	}
+
+	data->background_surface =
+	    cairo_image_surface_create(data->format, data->display_width, data->display_height);
+	if (cairo_surface_status(data->background_surface)) {
+		platsch_error("Failed to create background surface\n");
+		return EXIT_FAILURE;
+	}
+
+	cairo_surface_t *bk_img_surface = cairo_image_surface_create_from_png(file_name);
+
+	if (cairo_surface_status(bk_img_surface)) {
+		platsch_error("Failed to create cairo surface from %s\n", file_name);
+		return EXIT_FAILURE;
+	}
+
+	int image_width = cairo_image_surface_get_width(bk_img_surface);
+	int image_height = cairo_image_surface_get_height(bk_img_surface);
+	double scale_x = (double)data->display_width / image_width;
+	double scale_y = (double)data->display_height / image_height;
+
+	cairo_t *cr_background = cairo_create(data->background_surface);
+	cairo_scale(cr_background, scale_x, scale_y);
+	cairo_set_source_surface(cr_background, bk_img_surface, 0, 0);
+
+	cairo_paint(cr_background);
+	cairo_draw_text(cr_background, config);
+	return EXIT_SUCCESS;
+}
+
+static int cairo_create_symbol_surface(spinner_t *data, Config config)
+{
+	if (strlen(config.symbol) == 0)
+		return EXIT_FAILURE;
+
+	data->symbol_surface = cairo_image_surface_create_from_png(config.symbol);
+	if (cairo_surface_status(data->symbol_surface)) {
+		platsch_error("Failed loading %s\n", config.symbol);
+		data->symbol_surface = NULL;
+		return EXIT_FAILURE;
+	}
+	data->symbol_width = cairo_image_surface_get_width(data->symbol_surface);
+	data->symbol_height = cairo_image_surface_get_height(data->symbol_surface);
+
+	if (data->symbol_width > data->symbol_height) {
+		data->symbol_frames = data->symbol_width / data->symbol_height;
+		data->symbol_width = data->symbol_height;
+	} else {
+		data->symbol_frames = data->symbol_height / data->symbol_width;
+		data->symbol_height = data->symbol_width;
+	}
+
+	data->rotate_step = config.rotate_step;
+
+	if (config.symbol_x <0)
+		data->symbol_x = data->display_width / 2 - data->symbol_width / 2;
+	else
+		data->symbol_x = config.symbol_x;
+
+	if(config.symbol_y <0)
+		data->symbol_y = data->display_height / 2 - data->symbol_height / 2;
+	else
+		data->symbol_y = config.symbol_y;
+
+	return EXIT_SUCCESS;
+}
+
+int main(int argc, char *argv[])
+{
+	bool pid1 = getpid() == 1;
+	char **initsargv;
+	char file_name[PLATSCH_MAX_FILENAME_LENGTH];
+	Config config = DEFAULT_CONFIG;
+	const char *base = "splash";
+	const char *dir = "/usr/share/platsch";
+	const char *env;
+	int ret;
+
+	spinner_t *spinner_list = NULL, *spinner_node = NULL, *spinner_iter = NULL;
+	struct modeset_dev *iter;
+	struct timespec start, end;
+
+	env = getenv("platsch_directory");
+	if (env)
+		dir = env;
+
+	env = getenv("platsch_basename");
+	if (env)
+		base = env;
+
+	ret = snprintf(file_name, sizeof(file_name), "%s/spinner.conf", dir);
+	if (ret >= sizeof(file_name)) {
+		platsch_error("Failed to fit file_name\n");
+		return EXIT_FAILURE;
+	}
+
+	parseConfig(file_name, &config);
+
+	struct modeset_dev *modeset_list = platsch_init();
+
+	if (!modeset_list) {
+		platsch_error("Failed to initialize modeset\n");
+		return EXIT_FAILURE;
+	}
+
+	for (iter = modeset_list; iter; iter = iter->next) {
+		spinner_node = (spinner_t *)malloc(sizeof(spinner_t));
+		if (!spinner_node) {
+			platsch_error("Failed to allocate memory for spinner_node\n");
+			return EXIT_FAILURE;
+		}
+		memset(spinner_node, 0, sizeof(*spinner_node));
+
+		if (cairo_create_display_surface(iter, spinner_node))
+			return EXIT_FAILURE;
+
+		if (cairo_create_background_surface(spinner_node, config, dir, base))
+			return EXIT_FAILURE;
+
+		cairo_create_symbol_surface(spinner_node, config);
+
+		cairo_set_source_surface(spinner_node->disp_cr, spinner_node->background_surface, 0,
+					 0);
+
+		cairo_paint(spinner_node->disp_cr);
+		platsch_setup_display(iter);
+
+		spinner_node->next = spinner_list;
+		spinner_list = spinner_node;
+	}
+
+	platsch_drmDropMaster();
+
+	ret = fork();
+	if (ret < 0)
+		platsch_error("failed to fork for init: %m\n");
+	else if (ret == 0)
+		/*
+		 * Always fork to make sure we got the required
+		 * resources for drawing the animation in the child.
+		 */
+		goto drawing;
+
+	if (pid1) {
+		initsargv = calloc(sizeof(argv[0]), argc + 1);
+
+		if (!initsargv) {
+			platsch_error("failed to allocate argv for init\n");
+			return EXIT_FAILURE;
+		}
+		memcpy(initsargv, argv, argc * sizeof(argv[0]));
+		initsargv[0] = "/sbin/init";
+		initsargv[argc] = NULL;
+
+		execv("/sbin/init", initsargv);
+
+		platsch_error("failed to exec init: %m\n");
+
+		return EXIT_FAILURE;
+	}
+	return EXIT_SUCCESS;
+
+drawing:
+	// create cairo surface for drawing symbol to avoid frlickering.
+	cairo_surface_t *drawing_surface = cairo_image_surface_create(
+	    spinner_node->format, spinner_node->symbol_width, spinner_node->symbol_height);
+	if (cairo_surface_status(drawing_surface)) {
+		platsch_error("Failed to create drawing surface\n");
+		return EXIT_FAILURE;
+	}
+
+	cairo_t *cr_drawing = cairo_create(drawing_surface);
+
+	while (true) {
+		clock_gettime(CLOCK_MONOTONIC, &start);
+
+		for (spinner_iter = spinner_list; spinner_iter; spinner_iter = spinner_iter->next) {
+			if (spinner_node->symbol_surface == NULL) {
+				sleep(10);
+				continue;
+			}
+
+			if (spinner_node->symbol_frames > 1)
+				on_draw_sequence_animation(cr_drawing, spinner_iter);
+			else
+				on_draw_rotation_animation(cr_drawing, spinner_iter);
+
+			cairo_set_source_surface(spinner_iter->disp_cr, drawing_surface,
+						 spinner_iter->symbol_x, spinner_iter->symbol_y);
+			cairo_paint(spinner_iter->disp_cr);
+		}
+
+		clock_gettime(CLOCK_MONOTONIC, &end);
+		long elapsed_time =
+		    (end.tv_sec - start.tv_sec) * 1000000L + (end.tv_nsec - start.tv_nsec) / 1000;
+		long sleep_time = (1000000 / config.fps) - elapsed_time;
+
+		if (sleep_time > 0)
+			usleep(sleep_time);
+	}
+}
diff --git a/spinner_conf.c b/spinner_conf.c
new file mode 100644
index 0000000..b867811
--- /dev/null
+++ b/spinner_conf.c
@@ -0,0 +1,73 @@
+#include "spinner_conf.h"
+#include <errno.h>
+
+int parseConfig(const char *filename, Config *config)
+{
+	char *value_end;
+	char *value_start;
+	char key[MAX_LINE_LENGTH];
+	char line[MAX_LINE_LENGTH];
+	char value[MAX_LINE_LENGTH];
+	FILE *file;
+
+	file = fopen(filename, "r");
+	if (file == NULL) {
+		fprintf(stderr, "Unable to open file: %s\n", filename);
+		return -EFAULT;
+	}
+
+	while (fgets(line, sizeof(line), file)) {
+		if (strlen(line) > MAX_LINE_LENGTH) {
+			fprintf(stderr, "conf string too long\n");
+			continue;
+		}
+		if (line[0] != '#' && sscanf(line, "%[^=]=%[^\n]", key, value) == 2) {
+			value_start = strchr(line, '=') + 1;
+			value_end = line + strlen(line) - 1;
+
+			while (isspace(*value_start))
+				value_start++;
+			while (isspace(*value_end) || *value_end == '"')
+				value_end--;
+
+			if (*value_start == '"')
+				value_start++;
+			if (*value_end == '"')
+					value_end--;
+
+			if (value_end < value_start) {
+				memset(value, 0, sizeof(value));
+				continue;
+			} else {
+				strncpy(value, value_start, value_end - value_start + 1);
+				value[value_end - value_start + 1] = '\0';
+				value[sizeof(value) - 1] = '\0';
+			}
+			value[MAX_LINE_LENGTH - 1] = '\0';
+
+			if (strcmp(key, "symbol") == 0) {
+				strncpy(config->symbol, value, MAX_LINE_LENGTH);
+			} else if (strcmp(key, "text") == 0) {
+				strncpy(config->text, value, MAX_LINE_LENGTH);
+			} else if (strcmp(key, "fps") == 0) {
+				config->fps = atoi(value);
+			} else if (strcmp(key, "text_x") == 0) {
+				config->text_x = atoi(value);
+			} else if (strcmp(key, "text_y") == 0) {
+				config->text_y = atoi(value);
+			} else if (strcmp(key, "text_font") == 0) {
+				strncpy(config->text_font, value, MAX_LINE_LENGTH);
+			} else if (strcmp(key, "text_size") == 0) {
+				config->text_size = atoi(value);
+			} else if (strcmp(key, "rotate_step") == 0) {
+				config->rotate_step = atof(value);
+			} else if (strcmp(key, "symbol_x") == 0) {
+				config->symbol_x = atoi(value);
+			} else if (strcmp(key, "symbol_y") == 0) {
+				config->symbol_y = atoi(value);
+			}
+		}
+	}
+	fclose(file);
+	return 0;
+}
diff --git a/spinner_conf.h b/spinner_conf.h
new file mode 100644
index 0000000..c397b9a
--- /dev/null
+++ b/spinner_conf.h
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifndef __SPINNER_CONF_H__
+#define __SPINNER_CONF_H__
+
+
+#define MAX_LINE_LENGTH 128
+
+typedef struct {
+	char symbol[MAX_LINE_LENGTH];
+	char text_font[MAX_LINE_LENGTH];
+	char text[MAX_LINE_LENGTH];
+	float rotate_step;
+	int fps;
+	int symbol_x;
+	int symbol_y;
+	int text_size;
+	int text_x;
+	int text_y;
+} Config;
+
+int parseConfig(const char *filename, Config *config);
+
+#define DEFAULT_CONFIG { \
+	.symbol = "", \
+	.fps = 20, \
+	.text_x = 0, \
+	.text_y = 0, \
+	.text_font = "Sans", \
+	.text_size = 30, \
+	.text = "", \
+	.rotate_step = 0.1, \
+	.symbol_x = -1, \
+	.symbol_y = -1 \
+}
+#endif
-- 
2.34.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2024-06-26  8:41 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-06-23 11:54 [OSS-Tools] [PATCH platsch V6 1/5] platsch: constify draw_buffer LI Qingwu
2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 2/5] convert to meson build LI Qingwu
2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 3/5] platsch: split into platsch and libplatsch LI Qingwu
2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 4/5] Platsch: always fork child process LI Qingwu
2024-06-23 11:54 ` [OSS-Tools] [PATCH platsch V6 5/5] Add spinner executable for boot animation and text show LI Qingwu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox