File kmscon_PR75.patch of Package kmscon_fork

From 978c416a01e80effe64b9a5d31233ade4de2d11b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mirco=20M=C3=BCller?= <macslow@gmail.com>
Date: Mon, 24 Jul 2023 16:13:33 +0200
Subject: [PATCH 1/4] Ported output-rotation support from my (MacSlow) old
 kmscon-upstream branch to aetf's meson-based develop branch.

---
 COPYING                   |   1 +
 docs/man/kmscon.1.xml.in  |  28 +++++++++++
 src/kmscon_conf.c         |  26 +++++++---
 src/kmscon_conf.h         |   6 +++
 src/kmscon_terminal.c     | 103 +++++++++++++++++++++++++++++++++-----
 src/text.c                |  80 +++++++++++++++++++++++++++--
 src/text.h                |  16 +++++-
 src/text_gltex.c          |  73 +++++++++++++++++++++++++--
 src/text_gltex_atlas.vert |  12 ++++-
 9 files changed, 316 insertions(+), 29 deletions(-)

diff --git a/COPYING b/COPYING
index 3ad47b73..a9321fb9 100644
--- a/COPYING
+++ b/COPYING
@@ -15,6 +15,7 @@ This software was written by:
 	Hoàng Đức Hiếu <github@zahe.me>
 	Swift Geek <swiftgeek@gmail.com>
 	Aetf <aetf@unlimited-code.works>
+	Mirco Müller <macslow@gmail.com>
 
 = Copyright Notice =
 
diff --git a/docs/man/kmscon.1.xml.in b/docs/man/kmscon.1.xml.in
index c6ddb0f0..f3319fd0 100644
--- a/docs/man/kmscon.1.xml.in
+++ b/docs/man/kmscon.1.xml.in
@@ -430,6 +430,22 @@
                 (default: &lt;Ctrl&gt;&lt;Logo&gt;Return)</para>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><option>--grab-rotate-cw {grab}</option></term>
+        <listitem>
+          <para>Rotate output clock-wise: Normal -> Right -> Inverted -> Left and so on.
+                (default: &lt;Logo&gt;Plus)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--grab-rotate-ccw {grab}</option></term>
+        <listitem>
+          <para>Rotate output counter-clock-wise: Normal -> Left -> Inverted -> Right and so on.
+                (default: &lt;Logo&gt;Minus)</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
 
     <para>Video Options:</para>
@@ -482,6 +498,18 @@
                 only be used to debug render engines. (default: off)</para>
         </listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><option>--rotate {orientation}</option></term>
+        <listitem>
+          <para>Select the desired orientation of the output. Available orientations
+                are 'normal' meaning output is not rotated, 'right' meaning output is
+                rotated 90° clock-wise, 'inverted' meaning output is rotated 180°
+                clock-wise and 'left' meaning output is rotated 90° counter clock-wise.
+                This has only effect for the 'gltex' render-engine. Other render-engines
+                currently ignore this option. (default: 'normal')</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
 
     <para>Font Options:</para>
diff --git a/src/kmscon_conf.c b/src/kmscon_conf.c
index 2f3b1519..8877548b 100644
--- a/src/kmscon_conf.c
+++ b/src/kmscon_conf.c
@@ -134,14 +134,19 @@ static void print_help()
 		"\t                                  Close current session\n"
 		"\t    --grab-terminal-new <grab>  [<Ctrl><Logo>Return]\n"
 		"\t                                  Create new terminal session\n"
+		"\t    --grab-rotate-cw <grab>     [<Logo>Plus]\n"
+		"\t                                  Rotate output clock-wise\n"
+		"\t    --grab-rotate-ccw <grab>    [<Logo>Minus]\n"
+		"\t                                  Rotate output counter-clock-wise\n"
 		"\n"
 		"Video Options:\n"
-		"\t    --drm                   [on]    Use DRM if available\n"
-		"\t    --hwaccel               [off]   Use 3D hardware-acceleration if\n"
-		"\t                                    available\n"
-		"\t    --gpus={all,aux,primary}[all]   GPU selection mode\n"
-		"\t    --render-engine <eng>   [-]     Console renderer\n"
-		"\t    --render-timing         [off]   Print renderer timing information\n"
+		"\t    --drm                   [on]     Use DRM if available\n"
+		"\t    --hwaccel               [off]    Use 3D hardware-acceleration if\n"
+		"\t                                     available\n"
+		"\t    --gpus={all,aux,primary}[all]    GPU selection mode\n"
+		"\t    --render-engine <eng>   [-]      Console renderer\n"
+		"\t    --render-timing         [off]    Print renderer timing information\n"
+		"\t    --rotate <orientation>  [normal] normal, right, inverted, left\n"
 		"\n"
 		"Font Options:\n"
 		"\t    --font-engine <engine>  [pango]\n"
@@ -645,6 +650,12 @@ static struct conf_grab def_grab_session_close =
 static struct conf_grab def_grab_terminal_new =
 		CONF_SINGLE_GRAB(SHL_CONTROL_MASK | SHL_LOGO_MASK, XKB_KEY_Return);
 
+static struct conf_grab def_grab_rotate_cw =
+		CONF_SINGLE_GRAB(SHL_LOGO_MASK, XKB_KEY_plus);
+
+static struct conf_grab def_grab_rotate_ccw =
+		CONF_SINGLE_GRAB(SHL_LOGO_MASK, XKB_KEY_minus);
+
 static palette_t def_palette = {
 	[TSM_COLOR_BLACK]         = {   0,   0,   0 }, /* black */
 	[TSM_COLOR_RED]           = { 205,   0,   0 }, /* red */
@@ -728,12 +739,15 @@ int kmscon_conf_new(struct conf_ctx **out)
 		CONF_OPTION_GRAB(0, "grab-session-dummy", &conf->grab_session_dummy, &def_grab_session_dummy),
 		CONF_OPTION_GRAB(0, "grab-session-close", &conf->grab_session_close, &def_grab_session_close),
 		CONF_OPTION_GRAB(0, "grab-terminal-new", &conf->grab_terminal_new, &def_grab_terminal_new),
+		CONF_OPTION_GRAB(0, "grab-rotate-cw", &conf->grab_rotate_cw, &def_grab_rotate_cw),
+		CONF_OPTION_GRAB(0, "grab-rotate-ccw", &conf->grab_rotate_ccw, &def_grab_rotate_ccw),
 
 		/* Video Options */
 		CONF_OPTION_BOOL_FULL(0, "drm", aftercheck_drm, NULL, NULL, &conf->drm, true),
 		CONF_OPTION_BOOL(0, "hwaccel", &conf->hwaccel, false),
 		CONF_OPTION(0, 0, "gpus", &conf_gpus, NULL, NULL, NULL, &conf->gpus, KMSCON_GPU_ALL),
 		CONF_OPTION_STRING(0, "render-engine", &conf->render_engine, NULL),
+		CONF_OPTION_STRING(0, "rotate", &conf->rotate, "normal"),
 
 		/* Font Options */
 		CONF_OPTION_STRING(0, "font-engine", &conf->font_engine, "pango"),
diff --git a/src/kmscon_conf.h b/src/kmscon_conf.h
index 9ae9ecff..b7d0ae87 100644
--- a/src/kmscon_conf.h
+++ b/src/kmscon_conf.h
@@ -135,6 +135,10 @@ struct kmscon_conf_t {
 	struct conf_grab *grab_session_close;
 	/* terminal-new grab */
 	struct conf_grab *grab_terminal_new;
+	/* rotate output clock-wise grab */
+	struct conf_grab *grab_rotate_cw;
+	/* rotate output counter-clock-wise grab */
+	struct conf_grab *grab_rotate_ccw;
 
 	/* Video Options */
 	/* use DRM if available */
@@ -145,6 +149,8 @@ struct kmscon_conf_t {
 	unsigned int gpus;
 	/* render engine */
 	char *render_engine;
+	/* orientation/rotation of output */
+	char *rotate;
 
 	/* Font Options */
 	/* font engine */
diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c
index 508bd971..44efdf63 100644
--- a/src/kmscon_terminal.c
+++ b/src/kmscon_terminal.c
@@ -84,12 +84,16 @@ struct kmscon_terminal {
 	struct kmscon_font *bold_font;
 };
 
+static void terminal_resize(struct kmscon_terminal *term,
+                            unsigned int cols, unsigned int rows,
+                            bool force, bool notify);
+
 static void do_clear_margins(struct screen *scr)
 {
-	unsigned int w, h, sw, sh;
+	unsigned int h, sw, sh;
 	struct uterm_mode *mode;
 	struct tsm_screen_attr attr;
-	int dw, dh;
+	int dh;
 
 	mode = uterm_display_get_current(scr->disp);
 	if (!mode)
@@ -97,21 +101,30 @@ static void do_clear_margins(struct screen *scr)
 
 	sw = uterm_mode_get_width(mode);
 	sh = uterm_mode_get_height(mode);
-	w = scr->txt->font->attr.width * scr->txt->cols;
 	h = scr->txt->font->attr.height * scr->txt->rows;
-	dw = sw - w;
 	dh = sh - h;
 
 	tsm_vte_get_def_attr(scr->term->vte, &attr);
 
-	if (dw > 0)
-		uterm_display_fill(scr->disp, attr.br, attr.bg, attr.bb,
-				   w, 0,
-				   dw, h);
-	if (dh > 0)
-		uterm_display_fill(scr->disp, attr.br, attr.bg, attr.bb,
-				   0, h,
-				   sw, dh);
+	switch (scr->txt->orientation) {
+		case ORIENTATION_NORMAL:
+			uterm_display_fill(scr->disp, 0, 0, 0, 0, h, sw, dh);
+		break;
+
+		case ORIENTATION_RIGHT:
+			uterm_display_fill(scr->disp, 0, 0, 0, 0, 0, sw - h, sh);
+		break;
+
+		case ORIENTATION_INVERTED:
+			uterm_display_fill(scr->disp, 0, 0, 0, 0, 0, sw, dh);
+		break;
+
+		case ORIENTATION_LEFT:
+			uterm_display_fill(scr->disp, 0, 0, 0, h, 0, sw - h, sh);
+		break;
+
+		default : break;
+	}
 }
 
 static void do_redraw_screen(struct screen *scr)
@@ -276,6 +289,58 @@ static int font_set(struct kmscon_terminal *term)
 	return 0;
 }
 
+static void rotate_cw_screen(struct screen *scr)
+{
+	unsigned int orientation = kmscon_text_get_orientation(scr->txt);
+	orientation = (orientation + 1) % ORIENTATION_MAX;
+	if (orientation == ORIENTATION_UNDEFINED) orientation = ORIENTATION_NORMAL;
+	kmscon_text_rotate(scr->txt, orientation);
+}
+
+static void rotate_cw_all(struct kmscon_terminal *term)
+{
+	struct shl_dlist *iter;
+	struct screen *scr;
+
+	shl_dlist_for_each(iter, &term->screens) {
+		scr = shl_dlist_entry(iter, struct screen, list);
+		rotate_cw_screen(scr);
+		term->min_cols = 0;
+		term->min_rows = 0;
+		terminal_resize(term,
+						kmscon_text_get_cols(scr->txt),
+						kmscon_text_get_rows(scr->txt),
+						true,
+						true);
+	}
+}
+
+static void rotate_ccw_screen(struct screen *scr)
+{
+	unsigned int orientation = kmscon_text_get_orientation(scr->txt);
+	orientation = (orientation - 1) % ORIENTATION_MAX;
+	if (orientation == ORIENTATION_UNDEFINED) orientation = ORIENTATION_LEFT;
+	kmscon_text_rotate(scr->txt, orientation);
+}
+
+static void rotate_ccw_all(struct kmscon_terminal *term)
+{
+	struct shl_dlist *iter;
+	struct screen *scr;
+
+	shl_dlist_for_each(iter, &term->screens) {
+		scr = shl_dlist_entry(iter, struct screen, list);
+		rotate_ccw_screen(scr);
+		term->min_cols = 0;
+		term->min_rows = 0;
+		terminal_resize(term,
+						kmscon_text_get_cols(scr->txt),
+						kmscon_text_get_rows(scr->txt),
+						true,
+						true);
+	}
+}
+
 static int add_display(struct kmscon_terminal *term, struct uterm_display *disp)
 {
 	struct shl_dlist *iter;
@@ -313,7 +378,7 @@ static int add_display(struct kmscon_terminal *term, struct uterm_display *disp)
 	else
 		be = "bbulk";
 
-	ret = kmscon_text_new(&scr->txt, be);
+	ret = kmscon_text_new(&scr->txt, be, term->conf->rotate);
 	if (ret) {
 		log_error("cannot create text-renderer");
 		goto err_cb;
@@ -453,6 +518,18 @@ static void input_event(struct uterm_input *input,
 			++term->font_attr.points;
 		return;
 	}
+	if (conf_grab_matches(term->conf->grab_rotate_cw,
+				ev->mods, ev->num_syms, ev->keysyms)) {
+		rotate_cw_all(term);
+		ev->handled = true;
+		return;
+	}
+	if (conf_grab_matches(term->conf->grab_rotate_ccw,
+				ev->mods, ev->num_syms, ev->keysyms)) {
+		rotate_ccw_all(term);
+		ev->handled = true;
+		return;
+	}
 
 	/* TODO: xkbcommon supports multiple keysyms, but it is currently
 	 * unclear how this feature will be used. There is no keymap, which
diff --git a/src/text.c b/src/text.c
index 24b579b8..9b116e27 100644
--- a/src/text.c
+++ b/src/text.c
@@ -103,7 +103,7 @@ void kmscon_text_unregister(const char *name)
 	shl_register_remove(&text_reg, name);
 }
 
-static int new_text(struct kmscon_text *text, const char *backend)
+static int new_text(struct kmscon_text *text, const char *backend, unsigned int orientation)
 {
 	struct shl_register_record *record;
 	const char *name = backend ? backend : "<default>";
@@ -124,6 +124,7 @@ static int new_text(struct kmscon_text *text, const char *backend)
 
 	text->record = record;
 	text->ops = record->data;
+	text->orientation = orientation;
 
 	if (text->ops->init)
 		ret = text->ops->init(text);
@@ -143,10 +144,11 @@ static int new_text(struct kmscon_text *text, const char *backend)
  * kmscon_text_new:
  * @out: A pointer to the new text-renderer is stored here
  * @backend: Backend to use or NULL for default backend
+ * @rotate: Orientation ("normal", "inverted", "right" or "left") to use for output
  *
  * Returns: 0 on success, error code on failure
  */
-int kmscon_text_new(struct kmscon_text **out, const char *backend)
+int kmscon_text_new(struct kmscon_text **out, const char *backend, const char *rotate)
 {
 	struct kmscon_text *text;
 	int ret;
@@ -160,10 +162,31 @@ int kmscon_text_new(struct kmscon_text **out, const char *backend)
 		return -ENOMEM;
 	}
 
-	ret = new_text(text, backend);
+	text->orientation = ORIENTATION_UNDEFINED;
+
+	if (rotate) {
+		if (strncmp(rotate, "normal", 6) == 0) {
+			text->orientation = ORIENTATION_NORMAL;
+			log_debug("using: orientation: normal");
+		}
+		else if (strncmp(rotate, "right", 5) == 0) {
+			text->orientation = ORIENTATION_RIGHT;
+			log_debug("using: orientation: right");
+		}
+		else if (strncmp(rotate, "inverted", 8) == 0) {
+			text->orientation = ORIENTATION_INVERTED;
+			log_debug("using: orientation: inverted");
+		}
+		else if (strncmp(rotate, "left", 4) == 0) {
+			text->orientation = ORIENTATION_LEFT;
+			log_debug("using: orientation: left");
+		}
+	}
+
+	ret = new_text(text, backend, text->orientation);
 	if (ret) {
 		if (backend)
-			ret = new_text(text, NULL);
+			ret = new_text(text, NULL, text->orientation);
 		if (ret)
 			goto err_free;
 	}
@@ -333,6 +356,55 @@ unsigned int kmscon_text_get_rows(struct kmscon_text *txt)
 	return txt->rows;
 }
 
+/**
+ * kmscon_text_get_orientation:
+ * @txt: valid text renderer
+ * 
+ * With a valid @txt passed it, this returns the currently active orientation
+ * of the output/screen. Possible values are:
+ * 
+ *   - ORIENTATION_NORMAL
+ *   - ORIENTATION_RIGHT
+ *   - ORIENTATION_INVERTED
+ *   - ORIENTATION_LEFT
+ * 
+ * Returns: Current orientation enum or ORIENTATION_UNDEFINED if @txt is invalid
+ */
+unsigned int kmscon_text_get_orientation(struct kmscon_text *txt)
+{
+	if (!txt)
+		return ORIENTATION_UNDEFINED;
+
+	return txt->orientation;
+}
+
+/**
+ * kmscon_text_rotate:
+ * @txt: valid text renderer
+ * @orientation: enum value representing the desired output-orientation
+ * 
+ * Update the rotation/orientation of the text. It can be one of:
+ * 
+ *   - ORIENTATION_NORMAL
+ *   - ORIENTATION_RIGHT
+ *   - ORIENTATION_INVERTED
+ *   - ORIENTATION_LEFT
+ * 
+ * Returns: 0 on success, negative error code on failure.
+ */
+int kmscon_text_rotate(struct kmscon_text *txt, unsigned int orientation)
+{
+	int ret = 0;
+
+	if (orientation == ORIENTATION_UNDEFINED)
+		return -EINVAL;
+
+	if (txt->ops->rotate)
+		ret = txt->ops->rotate(txt, orientation);
+
+	return ret;
+}
+
 /**
  * kmscon_text_prepare:
  * @txt: valid text renderer
diff --git a/src/text.h b/src/text.h
index 570df6b7..269671e4 100644
--- a/src/text.h
+++ b/src/text.h
@@ -42,6 +42,15 @@
 
 /* text renderer */
 
+enum Orientation {
+	ORIENTATION_UNDEFINED = 0,
+	ORIENTATION_NORMAL,
+	ORIENTATION_RIGHT,
+	ORIENTATION_INVERTED,
+	ORIENTATION_LEFT,
+	ORIENTATION_MAX
+};
+
 struct kmscon_text;
 struct kmscon_text_ops;
 
@@ -57,6 +66,7 @@ struct kmscon_text {
 	unsigned int cols;
 	unsigned int rows;
 	bool rendering;
+	unsigned int orientation;
 };
 
 struct kmscon_text_ops {
@@ -66,6 +76,7 @@ struct kmscon_text_ops {
 	void (*destroy) (struct kmscon_text *txt);
 	int (*set) (struct kmscon_text *txt);
 	void (*unset) (struct kmscon_text *txt);
+	int (*rotate) (struct kmscon_text *txt, unsigned int orientation);
 	int (*prepare) (struct kmscon_text *txt);
 	int (*draw) (struct kmscon_text *txt,
 		     uint64_t id, const uint32_t *ch, size_t len,
@@ -79,7 +90,7 @@ struct kmscon_text_ops {
 int kmscon_text_register(const struct kmscon_text_ops *ops);
 void kmscon_text_unregister(const char *name);
 
-int kmscon_text_new(struct kmscon_text **out, const char *backend);
+int kmscon_text_new(struct kmscon_text **out, const char *backend, const char *rotate);
 void kmscon_text_ref(struct kmscon_text *txt);
 void kmscon_text_unref(struct kmscon_text *txt);
 
@@ -91,6 +102,9 @@ void kmscon_text_unset(struct kmscon_text *txt);
 unsigned int kmscon_text_get_cols(struct kmscon_text *txt);
 unsigned int kmscon_text_get_rows(struct kmscon_text *txt);
 
+unsigned int kmscon_text_get_orientation(struct kmscon_text *txt);
+int kmscon_text_rotate(struct kmscon_text *txt, unsigned int orientation);
+
 int kmscon_text_prepare(struct kmscon_text *txt);
 int kmscon_text_draw(struct kmscon_text *txt,
 		     uint64_t id, const uint32_t *ch, size_t len,
diff --git a/src/text_gltex.c b/src/text_gltex.c
index 4b132d21..d7d25136 100644
--- a/src/text_gltex.c
+++ b/src/text_gltex.c
@@ -42,6 +42,7 @@
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 #include <limits.h>
+#include <math.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <string.h>
@@ -105,6 +106,7 @@ struct gltex {
 	GLfloat advance_y;
 
 	struct gl_shader *shader;
+	GLuint uni_orientation;
 	GLuint uni_proj;
 	GLuint uni_atlas;
 	GLuint uni_advance_htex;
@@ -112,6 +114,8 @@ struct gltex {
 
 	unsigned int sw;
 	unsigned int sh;
+
+	GLfloat angle;
 };
 
 #define FONT_WIDTH(txt) ((txt)->font->attr.width)
@@ -186,6 +190,7 @@ static int gltex_set(struct kmscon_text *txt)
 	if (ret)
 		goto err_bold_htable;
 
+	gt->uni_orientation = gl_shader_get_uniform(gt->shader, "orientation");
 	gt->uni_proj = gl_shader_get_uniform(gt->shader, "projection");
 	gt->uni_atlas = gl_shader_get_uniform(gt->shader, "atlas");
 	gt->uni_advance_htex = gl_shader_get_uniform(gt->shader,
@@ -202,8 +207,13 @@ static int gltex_set(struct kmscon_text *txt)
 	gt->sw = uterm_mode_get_width(mode);
 	gt->sh = uterm_mode_get_height(mode);
 
-	txt->cols = gt->sw / FONT_WIDTH(txt);
-	txt->rows = gt->sh / FONT_HEIGHT(txt);
+	if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) {
+		txt->cols = gt->sw / FONT_WIDTH(txt);
+		txt->rows = gt->sh / FONT_HEIGHT(txt);
+	} else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) {
+		txt->cols = gt->sh / FONT_WIDTH(txt);
+		txt->rows = gt->sw / FONT_HEIGHT(txt);
+	}
 
 	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &s);
 	if (s <= 0)
@@ -523,6 +533,43 @@ static int find_glyph(struct kmscon_text *txt, struct glyph **out,
 	return ret;
 }
 
+static int gltex_rotate(struct kmscon_text *txt, unsigned int orientation)
+{
+	struct gltex *gt = txt->data;
+	int ret = 0;
+
+	txt->orientation = orientation;
+
+	if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) {
+		txt->cols = gt->sw / FONT_WIDTH(txt);
+		txt->rows = gt->sh / FONT_HEIGHT(txt);
+	} else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) {
+		txt->cols = gt->sh / FONT_WIDTH(txt);
+		txt->rows = gt->sw / FONT_HEIGHT(txt);
+	}
+
+	if (txt->orientation == ORIENTATION_NORMAL ) {
+		gt->angle = .0;
+	} else if (txt->orientation == ORIENTATION_INVERTED) {
+		gt->angle = 180.;
+	} else if (txt->orientation == ORIENTATION_RIGHT) {
+		gt->angle = 90.;
+	} else if (txt->orientation == ORIENTATION_LEFT) {
+		gt->angle = -90.;
+	}
+
+	if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) {
+		gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt);
+		gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt);
+	} else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) {
+		float aspect = (float) gt->sw / (float) gt->sh;
+		gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt) * aspect;
+		gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt) * (1./aspect);
+	}
+
+	return ret;
+}
+
 static int gltex_prepare(struct kmscon_text *txt)
 {
 	struct gltex *gt = txt->data;
@@ -540,8 +587,24 @@ static int gltex_prepare(struct kmscon_text *txt)
 		atlas->cache_num = 0;
 	}
 
-	gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt);
-	gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt);
+	if (txt->orientation == ORIENTATION_NORMAL ) {
+		gt->angle = .0;
+	} else if (txt->orientation == ORIENTATION_INVERTED) {
+		gt->angle = 180.;
+	} else if (txt->orientation == ORIENTATION_RIGHT) {
+		gt->angle = 90.;
+	} else if (txt->orientation == ORIENTATION_LEFT) {
+		gt->angle = -90.;
+	}
+
+	if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) {
+		gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt);
+		gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt);
+	} else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) {
+		float aspect = (float) gt->sw / (float) gt->sh;
+		gt->advance_x = 2.0 / gt->sw * FONT_WIDTH(txt) * aspect;
+		gt->advance_y = 2.0 / gt->sh * FONT_HEIGHT(txt) * (1./aspect);
+	}
 
 	return 0;
 }
@@ -648,6 +711,7 @@ static int gltex_render(struct kmscon_text *txt)
 
 	gl_m4_identity(mat);
 	glUniformMatrix4fv(gt->uni_proj, 1, GL_FALSE, mat);
+	glUniform1f(gt->uni_orientation, gt->angle);
 
 	glEnableVertexAttribArray(0);
 	glEnableVertexAttribArray(1);
@@ -693,6 +757,7 @@ struct kmscon_text_ops kmscon_text_gltex_ops = {
 	.destroy = gltex_destroy,
 	.set = gltex_set,
 	.unset = gltex_unset,
+	.rotate = gltex_rotate,
 	.prepare = gltex_prepare,
 	.draw = gltex_draw,
 	.render = gltex_render,
diff --git a/src/text_gltex_atlas.vert b/src/text_gltex_atlas.vert
index e99e1b4a..9bacae9f 100644
--- a/src/text_gltex_atlas.vert
+++ b/src/text_gltex_atlas.vert
@@ -31,6 +31,7 @@
  */
 
 uniform mat4 projection;
+uniform float orientation;
 
 attribute vec2 position;
 attribute vec2 texture_position;
@@ -41,9 +42,18 @@ varying vec2 texpos;
 varying vec3 fgcol;
 varying vec3 bgcol;
 
+vec2 opRotate(in vec2 p, in float degrees)
+{
+    float rad = radians(degrees);
+    float c = cos(rad);
+    float s = sin(rad);
+    return p * mat2(vec2(c, s), vec2(-s, c));
+}
+
 void main()
 {
-	gl_Position = projection * vec4(position, 0.0, 1.0);
+	vec2 rotatedPosition = opRotate(position, orientation);
+	gl_Position = projection * vec4(rotatedPosition, 0.0, 1.0);
 	texpos = texture_position;
 	fgcol = fgcolor;
 	bgcol = bgcolor;

From 9205158a737e94a07eb251f70c539a433bfd48f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mirco=20M=C3=BCller?= <macslow@gmail.com>
Date: Mon, 24 Jul 2023 16:16:26 +0200
Subject: [PATCH 2/4] Ported mouse-selection/cut/paste support from my
 (MacSlow) old kmscon-upstream branch to aetf's meson-based develop branch.

---
 src/kmscon_mouse.c     | 478 +++++++++++++++++++++++++++++++++++++++++
 src/kmscon_mouse.h     | 138 ++++++++++++
 src/kmscon_seat.c      |  23 +-
 src/kmscon_seat.h      |   5 +
 src/kmscon_terminal.c  | 122 +++++++++++
 src/meson.build        |   3 +
 src/mouse_pointer.frag |  39 ++++
 src/mouse_pointer.vert |  50 +++++
 src/text.c             |  25 +++
 src/text.h             |   4 +
 src/text_gltex.c       | 116 ++++++++++
 11 files changed, 1002 insertions(+), 1 deletion(-)
 create mode 100644 src/kmscon_mouse.c
 create mode 100644 src/kmscon_mouse.h
 create mode 100644 src/mouse_pointer.frag
 create mode 100644 src/mouse_pointer.vert

diff --git a/src/kmscon_mouse.c b/src/kmscon_mouse.c
new file mode 100644
index 00000000..2e618a85
--- /dev/null
+++ b/src/kmscon_mouse.c
@@ -0,0 +1,478 @@
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "kmscon_mouse.h"
+#include "kmscon_seat.h"
+#include "shl_log.h"
+#include "text.h"
+
+// utility functions for internal (kmscon_mouse) use only
+int mouse_ready_to_read(int handle);
+void mouse_hide_timer_cb(struct ev_timer *timer, uint64_t num, void *data);
+void handle_clicks (struct kmscon_mouse_info* mouse, int button);
+void update_mouse_button_state (struct kmscon_mouse_info* mouse,
+                                int button,
+                                char button_mask,
+                                char buffer);
+void mouse_query_timer_cb(struct ev_timer *timer, uint64_t num, void *data);
+
+// public API
+struct kmscon_mouse_info* kmscon_mouse_init(struct ev_eloop* eloop,
+                                            struct kmscon_seat* seat)
+{
+	struct kmscon_mouse_info* mouse;
+	size_t size = sizeof(struct kmscon_mouse_info);
+	mouse = (struct kmscon_mouse_info*) calloc(1, size);
+	if (!mouse)
+		goto err;
+
+	mouse->seat = seat;
+	mouse->state[KMSCON_MOUSE_BUTTON_LEFT] = KMSCON_MOUSE_BUTTON_RELEASED;
+	mouse->state[KMSCON_MOUSE_BUTTON_MIDDLE] = KMSCON_MOUSE_BUTTON_RELEASED;
+	mouse->state[KMSCON_MOUSE_BUTTON_RIGHT] = KMSCON_MOUSE_BUTTON_RELEASED;
+	gettimeofday(&mouse->last_up, NULL);
+	gettimeofday(&mouse->last_down, NULL);
+	gettimeofday(&mouse->click, NULL);
+
+	size = sizeof(struct kmscon_selection_info);
+	mouse->selection = (struct kmscon_selection_info*) calloc(1, size);
+	if (!mouse->selection)
+		goto err;
+
+	int use_mouse = true;
+	char devname[MAX_DEVNAME_SIZE];
+	int length = sprintf (devname, DEVNAME_TEMPLATE);
+	if (length > MAX_DEVNAME_SIZE) {
+		log_warn ("Error while creating device string!\n");
+		use_mouse = false;
+	}
+
+	mouse->device = open (devname, O_RDONLY | O_NONBLOCK);
+	if (mouse->device < 0) {
+		log_warn ("Error while opening device '%s'!\n", devname);
+		use_mouse = false;
+	}
+
+	if (use_mouse) {
+		mouse->hide = true;
+
+		// mouse-query timer
+		mouse->query_timer_spec.it_interval.tv_sec  = 0;
+		mouse->query_timer_spec.it_interval.tv_nsec = 10*1000*1000;
+		mouse->query_timer_spec.it_value.tv_sec  = 0;
+		mouse->query_timer_spec.it_value.tv_nsec = 10*1000*1000;
+
+		int ret = ev_timer_new(&mouse->query_timer,
+							   &mouse->query_timer_spec,
+							   mouse_query_timer_cb,
+							   mouse,
+							   NULL,
+							   NULL);
+		if (ret) {
+			log_error("cannot create mouse-query timer: %d", ret);
+			goto err;
+		}
+		ev_timer_enable(mouse->query_timer);
+
+		ret = ev_eloop_add_timer(eloop, mouse->query_timer);
+		if (ret) {
+			log_error("cannot add mouse-query timer to event-loop: %d", ret);
+			goto err;
+		}
+
+		// mouse-hide timer
+		mouse->hide_timer_spec.it_interval.tv_sec  = 5;
+		mouse->hide_timer_spec.it_interval.tv_nsec = 0;
+		mouse->hide_timer_spec.it_value.tv_sec  = 5;
+		mouse->hide_timer_spec.it_value.tv_nsec = 0;
+
+		ret = ev_timer_new(&mouse->hide_timer,
+						   &mouse->hide_timer_spec,
+						   mouse_hide_timer_cb,
+						   mouse,
+						   NULL,
+						   NULL);
+		if (ret) {
+			log_error("cannot create mouse-hide timer: %d", ret);
+			goto err;
+		}
+		ev_timer_enable(mouse->hide_timer);
+
+		ret = ev_eloop_add_timer(eloop, mouse->hide_timer);
+		if (ret) {
+			log_error("cannot add mouse-hide timer to event-loop: %d", ret);
+			goto err;
+		}
+	}
+
+	return mouse;
+
+err:
+	kmscon_mouse_cleanup(mouse);
+	return NULL;
+}
+
+void kmscon_mouse_cleanup(struct kmscon_mouse_info* mouse)
+{
+	if (mouse) {
+		if (mouse->selection) {
+			free(mouse->selection);
+		}
+		free(mouse);
+	}
+}
+
+void kmscon_mouse_set_mapping(struct kmscon_mouse_info* mouse,
+                              struct uterm_display* disp,
+                              struct kmscon_text* txt)
+{
+	if (mouse && disp && txt) {
+		mouse->disp = disp;
+		mouse->txt = txt;
+	}
+}
+
+int kmscon_mouse_get_x(struct kmscon_mouse_info* mouse)
+{
+	int x = 0;
+	struct uterm_mode *mode = uterm_display_get_current(mouse->disp);
+	int sw = uterm_mode_get_width(mode);
+	int sh = uterm_mode_get_height(mode);
+	float fw = mouse->txt->font->attr.width;
+
+	if (mouse && mouse->disp && mouse->txt) {
+
+		if (mouse->txt->orientation == ORIENTATION_NORMAL ||
+			mouse->txt->orientation == ORIENTATION_INVERTED) {
+
+			float pixel_w = 2.f / (float) sw;
+			float cursor_x = (1.f + mouse->x)/pixel_w;
+			cursor_x = cursor_x - fmodf(cursor_x, 1.f);
+			x = (int) cursor_x/fw;
+		}
+
+		if (mouse->txt->orientation == ORIENTATION_RIGHT ||
+			mouse->txt->orientation == ORIENTATION_LEFT) {
+
+			float pixel_w = 2.f / (float) sh;
+			float cursor_x = (1.f + mouse->x)/pixel_w;
+			cursor_x = cursor_x - fmodf(cursor_x, 1.f);
+			x = (int) cursor_x/fw;
+		}
+
+		x = (x >= mouse->txt->cols) ? mouse->txt->cols - 1 : x;
+	}
+
+	return x;
+}
+
+int kmscon_mouse_get_y(struct kmscon_mouse_info* mouse)
+{
+	int y = 0;
+	struct uterm_mode *mode = uterm_display_get_current(mouse->disp);
+	int sw = uterm_mode_get_width(mode);
+	int sh = uterm_mode_get_height(mode);
+	float fh = mouse->txt->font->attr.height;
+
+	if (mouse && mouse->disp && mouse->txt) {
+
+		if (mouse->txt->orientation == ORIENTATION_NORMAL ||
+			mouse->txt->orientation == ORIENTATION_INVERTED) {
+
+			float pixel_h = 2.f / (float) sh;
+			float cursor_y = fabsf(-1.f + mouse->y)/pixel_h;
+			cursor_y = cursor_y - fmodf(cursor_y, 1.f);
+			y = (int) cursor_y/fh;
+		}
+
+		if (mouse->txt->orientation == ORIENTATION_RIGHT ||
+			mouse->txt->orientation == ORIENTATION_LEFT) {
+
+			float pixel_h = 2.f / (float) sw;
+			float cursor_y = fabsf(-1.f + mouse->y)/pixel_h;
+			cursor_y = cursor_y - fmodf(cursor_y, 1.f);
+			y = (int) cursor_y/fh;
+		}
+
+		y = (y >= mouse->txt->rows) ? mouse->txt->rows - 1 : y;
+	}
+
+	return y;
+}
+
+int kmscon_mouse_is_clicked(struct kmscon_mouse_info* mouse,
+                            int button)
+{
+	if (mouse && button <= 3 && button >= 0)
+		return mouse->clicked[button];
+
+	return 0;
+}
+
+void kmscon_mouse_clear_clicked(struct kmscon_mouse_info* mouse,
+                                int button)
+{
+	if (mouse) {
+		mouse->clicked[button] = false;
+	}
+}
+
+int kmscon_mouse_is_dbl_clicked(struct kmscon_mouse_info* mouse,
+                                int button)
+{
+	if (mouse && button <= 3 && button >= 0)
+		return mouse->dbl_clicked[button];
+
+	return 0;
+}
+
+void kmscon_mouse_clear_dbl_clicked(struct kmscon_mouse_info* mouse,
+                                    int button)
+{
+	if (mouse) {
+		mouse->dbl_clicked[button] = false;
+	}
+}
+
+int kmscon_mouse_is_up(struct kmscon_mouse_info* mouse,
+                       int button)
+{
+	if (mouse && button <= 3 && button >= 0)
+		return (mouse->state[button] == KMSCON_MOUSE_BUTTON_UP);
+
+	return 0;
+}
+
+int kmscon_mouse_is_down(struct kmscon_mouse_info* mouse,
+                         int button)
+{
+	if (mouse && button <= 3 && button >= 0)
+		return (mouse->state[button] == KMSCON_MOUSE_BUTTON_DOWN);
+
+	return 0;
+}
+
+int kmscon_mouse_is_released(struct kmscon_mouse_info* mouse,
+                             int button)
+{
+	if (mouse && button <= 3 && button >= 0)
+		return (mouse->state[button] == KMSCON_MOUSE_BUTTON_RELEASED);
+
+	return 0;
+}
+
+int kmscon_mouse_is_pressed(struct kmscon_mouse_info* mouse,
+                            int button)
+{
+	if (mouse && button <= 3 && button >= 0)
+		return (mouse->state[button] == KMSCON_MOUSE_BUTTON_PRESSED);
+
+	return 0;
+}
+
+int kmscon_mouse_is_hidden(struct kmscon_mouse_info* mouse)
+{
+	if (mouse)
+		return mouse->hide;
+
+	return 0;
+}
+
+void kmscon_mouse_selection_copy(struct kmscon_mouse_info* mouse,
+                                 struct tsm_screen *console)
+{
+	if (mouse && console) {
+		int length = tsm_screen_selection_copy(console,
+											   &mouse->selection->buffer);
+		mouse->selection->buffer_length = length;
+	}
+}
+
+int kmscon_mouse_is_selection_empty(struct kmscon_mouse_info* mouse)
+{
+	if (!mouse || !mouse->selection)
+		return true;
+
+	if (mouse->selection->buffer_length == 0)
+		return true;
+	else
+		return false;
+}
+
+// internal API/utility functions
+int mouse_ready_to_read(int handle)
+{
+	const struct timespec timeout = {0, 3*1000*1000};
+	fd_set readfds;
+
+	FD_ZERO(&readfds);
+	FD_SET(handle, &readfds);
+	int result = pselect(handle + 1, &readfds, NULL, NULL, &timeout, NULL);
+	if (result == -1) {
+		log_error("There was an error trying to check for available mouse-data.");
+		return 0;
+	} else if (result == 0) {
+		return 0;
+	} else {
+		result = FD_ISSET(handle, &readfds);
+		if (result) {
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+void mouse_hide_timer_cb(struct ev_timer *timer,
+						 uint64_t num,
+						 void *data)
+{
+	if (!data) {
+		log_warn("No valid pointer passed to mouse_hide_timer_cb().");
+		return;
+	}
+
+	struct kmscon_mouse_info* mouse = NULL;
+	mouse = (struct kmscon_mouse_info*) data;
+	mouse->hide = true;
+}
+
+void handle_clicks (struct kmscon_mouse_info* mouse, int button)
+{
+	if (!mouse)
+		return;
+
+	time_t seconds = mouse->last_down.tv_sec;
+	suseconds_t useconds = mouse->last_down.tv_usec;
+	gettimeofday(&mouse->last_up, NULL);
+
+	// check for click
+	if (seconds == mouse->last_up.tv_sec &&
+		mouse->last_up.tv_usec - useconds <= 150000) {
+
+		mouse->clicked[button] = true;
+
+		// check for double-click
+		time_t dbl_seconds = mouse->click.tv_sec;
+		suseconds_t dbl_useconds = mouse->click.tv_usec;
+		gettimeofday(&mouse->click, NULL);
+
+		if (dbl_seconds == mouse->click.tv_sec &&
+			mouse->click.tv_usec - dbl_useconds <= 300000) {
+
+			mouse->dbl_clicked[button] = true;
+		}
+	}
+}
+
+void update_mouse_button_state(struct kmscon_mouse_info* mouse,
+                               int button,
+                               char button_mask,
+                               char buffer)
+{
+	if (!mouse)
+		return;
+
+	int new_state = mouse->state[button];
+
+	// released -> down
+	if (mouse->state[button] == KMSCON_MOUSE_BUTTON_RELEASED &&
+		(buffer & button_mask)) {
+		new_state = KMSCON_MOUSE_BUTTON_DOWN;
+		gettimeofday(&mouse->last_down, NULL);
+	}
+
+	// down -> pressed
+	if (mouse->state[button] == KMSCON_MOUSE_BUTTON_DOWN &&
+		(buffer & button_mask)) {
+		new_state = KMSCON_MOUSE_BUTTON_PRESSED;
+	}
+
+	// pressed -> up
+	if (mouse->state[button] == KMSCON_MOUSE_BUTTON_PRESSED &&
+		!(buffer & button_mask)) {
+		new_state = KMSCON_MOUSE_BUTTON_UP;
+	}
+
+	// up -> released
+	if (mouse->state[button] == KMSCON_MOUSE_BUTTON_UP &&
+		!(buffer & button_mask)) {
+		new_state = KMSCON_MOUSE_BUTTON_RELEASED;
+		handle_clicks (mouse, button);
+	}
+
+	// edge-case (no mouse motion): down -> released
+	if (mouse->state[button] == KMSCON_MOUSE_BUTTON_DOWN &&
+		!(buffer & button_mask)) {
+		new_state = KMSCON_MOUSE_BUTTON_RELEASED;
+		handle_clicks (mouse, button);
+	}
+
+	// edge-case (no mouse motion): up -> pressed
+	if (mouse->state[button] == KMSCON_MOUSE_BUTTON_UP &&
+		(buffer & button_mask)) {
+		new_state = KMSCON_MOUSE_BUTTON_PRESSED;
+	}
+
+	mouse->state[button] = new_state;
+}
+
+void mouse_query_timer_cb(struct ev_timer *timer, uint64_t num, void *data)
+{
+	if (!data) {
+		log_warn("No valid pointer passed to mouse_query_timer_cb().");
+		return;
+	}
+
+	struct kmscon_mouse_info* mouse = (struct kmscon_mouse_info*) data;
+	ssize_t size = 0;
+	short relative_x = 0;
+	short relative_y = 0;
+	char buffer[BUFFER_SIZE];
+
+	if (mouse_ready_to_read (mouse->device)) {
+		size = read(mouse->device, buffer, sizeof (buffer));
+		if (size != BUFFER_SIZE) {
+			log_error("Error reading device event... buffer-size mismatch!");
+			return;
+		}
+		relative_x = (short) buffer[1];
+		relative_y = (short) buffer[2];
+		mouse->x += .0025 * (float) relative_x/MOTION_SCALE;
+		mouse->y += .0025 * (float) relative_y/MOTION_SCALE;
+
+		// limit 'normalized' coordinates to the extents of the GL-viewport
+		if (mouse->x < -1.f) mouse->x = -1.f;
+		if (mouse->x > 1.f) mouse->x = 1.f;
+		if (mouse->y < -1.f) mouse->y = -1.f;
+		if (mouse->y > 1.f) mouse->y = 1.f;
+
+		update_mouse_button_state (mouse,
+								   KMSCON_MOUSE_BUTTON_LEFT,
+								   BUTTON_MASK_LEFT,
+								   buffer[0]);
+		update_mouse_button_state (mouse,
+								   KMSCON_MOUSE_BUTTON_MIDDLE,
+								   BUTTON_MASK_MIDDLE,
+								   buffer[0]);
+		update_mouse_button_state (mouse,
+								   KMSCON_MOUSE_BUTTON_RIGHT,
+								   BUTTON_MASK_RIGHT,
+								   buffer[0]);
+
+		mouse->hide = false;
+
+		// FIXME: Triggering the refresh like this works, but
+		// it's not an elegant solution. Using an event would
+		// be nicer, but I did not yet fully grasp the pile of
+		// event-loops kmscon uses. There has to be a way to
+		// trigger a refresh via the event-loops of uterm_video.
+		kmscon_seat_refresh_display(mouse->seat, mouse->disp);
+	}
+}
diff --git a/src/kmscon_mouse.h b/src/kmscon_mouse.h
new file mode 100644
index 00000000..ea0e5b7c
--- /dev/null
+++ b/src/kmscon_mouse.h
@@ -0,0 +1,138 @@
+/*
+ * Mouse
+ *
+ * Copyright (c) 2023 Mirco Müller <macslow@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Mouse
+ *
+ * Support for mouse/pointer-devices (usb-mice, trackpoints,
+ * trackpads etc.) and text copy&paste à la gpm is implemented here.
+ *
+ * You can get x- and y-coordinates (in character-cells) and check if
+ * LMB, MMB or RMB are up, down, pressed or released. You can also check
+ * for single- or double-click of LMB, MMB or RMB.
+ *
+ * There should only ever be one kmscon_mouse_info structure be
+ * created via kmscon_mouse_init() in all of kmscon. It will handle
+ * everything that's plugged into the system via the Linux kernels
+ * input sub-system.
+ *
+ * It is written in a way to be easy to read and understand, not waste
+ * cpu-cycles and just support enough features in order to implement
+ * gpm-like cursor- and copy&paste-functionality. Take note that scroll-
+ * -wheels are currently NOT supported.
+ *
+ * Do not use this in a general UI-toolkit manner like mouse-support in
+ * Qt, gtk+ or the like.
+ */
+
+#ifndef KMSCON_MOUSE_H
+#define KMSCON_MOUSE_H
+
+#include <libtsm.h>
+#include <stdint.h>
+#include <time.h>
+
+#include "eloop.h"
+
+#define MAX_DEVNAME_SIZE 16
+#define DEVNAME_TEMPLATE "/dev/input/mice"
+#define BUTTON_MASK_LEFT      0x01
+#define BUTTON_MASK_RIGHT     0x02
+#define BUTTON_MASK_MIDDLE    0x04
+#define MOTION_SCALE     2
+#define BUFFER_SIZE      3
+
+#define KMSCON_MOUSE_BUTTON_LEFT   0
+#define KMSCON_MOUSE_BUTTON_MIDDLE 1
+#define KMSCON_MOUSE_BUTTON_RIGHT  2
+
+typedef enum {
+	KMSCON_MOUSE_BUTTON_UNDEFINED = -1,
+	KMSCON_MOUSE_BUTTON_PRESSED   = 0,
+	KMSCON_MOUSE_BUTTON_RELEASED  = 1,
+	KMSCON_MOUSE_BUTTON_DOWN      = 2,
+	KMSCON_MOUSE_BUTTON_UP        = 3
+} kmscon_mouse_button_state;
+
+struct kmscon_selection_info {
+	char* buffer;
+	int buffer_length;
+};
+
+struct kmscon_mouse_info {
+	struct kmscon_selection_info* selection;
+
+	struct ev_timer* query_timer;
+	struct itimerspec query_timer_spec;
+
+	struct ev_timer* hide_timer;
+	struct itimerspec hide_timer_spec;
+
+	int device;
+	float x;
+	float y;
+	int hide;
+
+	kmscon_mouse_button_state state[3];
+	int clicked[3];
+	int dbl_clicked[3];
+
+	struct timeval last_up;
+	struct timeval last_down;
+	struct timeval click;
+
+	struct uterm_display* disp;
+	struct kmscon_text* txt;
+	struct kmscon_seat* seat;
+};
+
+struct kmscon_mouse_info* kmscon_mouse_init(struct ev_eloop* eloop,
+                                            struct kmscon_seat* seat);
+void kmscon_mouse_cleanup(struct kmscon_mouse_info* mouse);
+
+void kmscon_mouse_set_mapping(struct kmscon_mouse_info* mouse,
+							  struct uterm_display* disp,
+							  struct kmscon_text* txt);
+
+int kmscon_mouse_get_x(struct kmscon_mouse_info* mouse);
+int kmscon_mouse_get_y(struct kmscon_mouse_info* mouse);
+
+int kmscon_mouse_is_clicked(struct kmscon_mouse_info* mouse, int button);
+void kmscon_mouse_clear_clicked(struct kmscon_mouse_info* mouse, int button);
+int kmscon_mouse_is_dbl_clicked(struct kmscon_mouse_info* mouse, int button);
+void kmscon_mouse_clear_dbl_clicked(struct kmscon_mouse_info* mouse, int button);
+
+int kmscon_mouse_is_up(struct kmscon_mouse_info* mouse, int button);
+int kmscon_mouse_is_down(struct kmscon_mouse_info* mouse, int button);
+int kmscon_mouse_is_released(struct kmscon_mouse_info* mouse, int button);
+int kmscon_mouse_is_pressed(struct kmscon_mouse_info* mouse, int button);
+int kmscon_mouse_is_hidden(struct kmscon_mouse_info* mouse);
+
+void kmscon_mouse_selection_copy(struct kmscon_mouse_info* mouse,
+								 struct tsm_screen *console);
+
+int kmscon_mouse_is_selection_empty(struct kmscon_mouse_info* mouse);
+
+#endif /* KMSCON_MOUSE_H */
diff --git a/src/kmscon_seat.c b/src/kmscon_seat.c
index 56a65366..bb02f551 100644
--- a/src/kmscon_seat.c
+++ b/src/kmscon_seat.c
@@ -30,12 +30,14 @@
  */
 
 #include <errno.h>
+#include <fcntl.h>
 #include <stdlib.h>
 #include <string.h>
 #include "conf.h"
 #include "eloop.h"
 #include "kmscon_conf.h"
 #include "kmscon_dummy.h"
+#include "kmscon_mouse.h"
 #include "kmscon_seat.h"
 #include "kmscon_terminal.h"
 #include "shl_dlist.h"
@@ -96,6 +98,8 @@ struct kmscon_seat {
 
 	unsigned int async_schedule;
 
+	struct kmscon_mouse_info* mouse;
+
 	kmscon_seat_cb_t cb;
 	void *data;
 };
@@ -680,7 +684,7 @@ int kmscon_seat_new(struct kmscon_seat **out,
 		    void *data)
 {
 	struct kmscon_seat *seat;
-	int ret;
+	int ret = 0;
 	const char *locale;
 	char *keymap, *compose_file;
 	size_t compose_file_len;
@@ -699,6 +703,12 @@ int kmscon_seat_new(struct kmscon_seat **out,
 	shl_dlist_init(&seat->displays);
 	shl_dlist_init(&seat->sessions);
 
+	seat->mouse = kmscon_mouse_init(seat->eloop, seat);
+	if (!seat->mouse) {
+		log_error("failed to initalize mouse");
+		goto err_free;
+	}
+
 	seat->name = strdup(seatname);
 	if (!seat->name) {
 		log_error("cannot copy string");
@@ -783,6 +793,7 @@ int kmscon_seat_new(struct kmscon_seat **out,
 err_name:
 	free(seat->name);
 err_free:
+	kmscon_mouse_cleanup(seat->mouse);
 	free(seat);
 	return ret;
 }
@@ -824,6 +835,8 @@ void kmscon_seat_free(struct kmscon_seat *seat)
 	uterm_input_unregister_cb(seat->input, seat_input_event, seat);
 	uterm_input_unref(seat->input);
 	kmscon_conf_free(seat->conf_ctx);
+	free(seat->mouse->selection);
+	free(seat->mouse);
 	free(seat->name);
 	uterm_vt_master_unref(seat->vtm);
 	ev_eloop_unref(seat->eloop);
@@ -960,6 +973,14 @@ struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat)
 	return seat->conf_ctx;
 }
 
+struct kmscon_mouse_info *kmscon_seat_get_mouse(struct kmscon_seat *seat)
+{
+   if (!seat)
+       return NULL;
+
+   return seat->mouse;
+}
+
 void kmscon_seat_schedule(struct kmscon_seat *seat, unsigned int id)
 {
 	struct shl_dlist *iter;
diff --git a/src/kmscon_seat.h b/src/kmscon_seat.h
index 81169686..11d9efa2 100644
--- a/src/kmscon_seat.h
+++ b/src/kmscon_seat.h
@@ -34,6 +34,8 @@
 
 #include <stdbool.h>
 #include <stdlib.h>
+#include <sys/time.h>
+#include "kmscon_mouse.h"
 #include "conf.h"
 #include "eloop.h"
 #include "uterm_input.h"
@@ -98,6 +100,9 @@ struct uterm_input *kmscon_seat_get_input(struct kmscon_seat *seat);
 struct ev_eloop *kmscon_seat_get_eloop(struct kmscon_seat *seat);
 struct conf_ctx *kmscon_seat_get_conf(struct kmscon_seat *seat);
 
+struct kmscon_mouse_info *kmscon_seat_get_mouse(struct kmscon_seat *seat);
+struct shl_dlist kmscon_seat_get_displays(struct kmscon_seat *seat);
+
 void kmscon_seat_schedule(struct kmscon_seat *seat, unsigned int id);
 
 int kmscon_seat_register_session(struct kmscon_seat *seat,
diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c
index 44efdf63..30e1d9ac 100644
--- a/src/kmscon_terminal.c
+++ b/src/kmscon_terminal.c
@@ -33,11 +33,21 @@
 #include <errno.h>
 #include <inttypes.h>
 #include <libtsm.h>
+#include <math.h>
 #include <stdlib.h>
 #include <string.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/select.h>
+#include <linux/input.h>
+
 #include "conf.h"
 #include "eloop.h"
 #include "kmscon_conf.h"
+#include "kmscon_mouse.h"
 #include "kmscon_seat.h"
 #include "kmscon_terminal.h"
 #include "pty.h"
@@ -82,6 +92,9 @@ struct kmscon_terminal {
 	struct kmscon_font_attr font_attr;
 	struct kmscon_font *font;
 	struct kmscon_font *bold_font;
+
+	struct kmscon_mouse_info* mouse;
+	struct kmscon_selection_info* selection;
 };
 
 static void terminal_resize(struct kmscon_terminal *term,
@@ -127,6 +140,107 @@ static void do_clear_margins(struct screen *scr)
 	}
 }
 
+static void handle_mouse_word_selection(struct kmscon_mouse_info* mouse,
+										struct kmscon_text* text,
+										struct tsm_screen* console)
+{
+	if (!mouse || !text || !console)
+		return;
+
+	// on left double-click trigger word-wise selection of text
+	if (kmscon_mouse_is_dbl_clicked(mouse, KMSCON_MOUSE_BUTTON_LEFT)) {
+		int from_x = kmscon_mouse_get_x(mouse);
+		int from_y = kmscon_mouse_get_y(mouse);
+		tsm_screen_selection_reset(console);
+		tsm_screen_selection_start(console, 0, from_y);
+		tsm_screen_selection_target(console,
+									text->cols - 1, from_y);
+		kmscon_mouse_selection_copy(mouse, console);
+		tsm_screen_selection_reset(console);
+
+		char* buf = mouse->selection->buffer;
+
+		int start_x = 0;
+		int target_x = 0;
+
+		// find trailing space or end of line
+		for (target_x = from_x; target_x <= text->cols - 1; ++target_x) {
+			if (buf[target_x] == ' ' || buf[target_x] == '\0') {
+				--target_x;
+				break;
+			}
+		}
+
+		// find leading space of start of line
+		for (start_x = from_x; start_x >= 0; --start_x) {
+			if (buf[start_x] == ' ') {
+				++start_x;
+				break;
+			} else if (start_x == 0) {
+				break;
+			}
+		}
+
+		// mark word under cusor and update selection-buffer
+		tsm_screen_selection_start(console, start_x, from_y);
+		tsm_screen_selection_target(console, target_x, from_y);
+		kmscon_mouse_selection_copy(mouse, console);
+		kmscon_mouse_clear_dbl_clicked(mouse, KMSCON_MOUSE_BUTTON_LEFT);
+	}
+}
+
+static void handle_mouse_random_selection(struct kmscon_mouse_info* mouse,
+										  struct kmscon_terminal* terminal)
+{
+	if (!mouse || !terminal)
+		return;
+
+	// paste current selection at current cursor position from buffer
+	if (kmscon_mouse_is_clicked (mouse, KMSCON_MOUSE_BUTTON_MIDDLE) &&
+		!kmscon_mouse_is_selection_empty (mouse)) {
+			kmscon_pty_write(terminal->pty,
+							 mouse->selection->buffer,
+							 mouse->selection->buffer_length);
+			tsm_screen_selection_reset(terminal->console);
+			kmscon_mouse_clear_clicked(mouse, KMSCON_MOUSE_BUTTON_MIDDLE);
+	}
+
+	// mark start of new selection
+	if (kmscon_mouse_is_down(mouse, KMSCON_MOUSE_BUTTON_LEFT)) {
+		int from_x = kmscon_mouse_get_x(mouse);
+		int from_y = kmscon_mouse_get_y(mouse);
+		tsm_screen_selection_reset(terminal->console);
+		tsm_screen_selection_start(terminal->console, from_x, from_y);
+		tsm_screen_selection_target(terminal->console, from_x, from_y);
+	} else if (kmscon_mouse_is_pressed(mouse, KMSCON_MOUSE_BUTTON_LEFT)) {
+		tsm_screen_selection_target(terminal->console,
+									kmscon_mouse_get_x(mouse),
+									kmscon_mouse_get_y(mouse));
+	}
+
+	// copy new selection to buffer
+	if (kmscon_mouse_is_up(mouse, KMSCON_MOUSE_BUTTON_LEFT)) {
+		kmscon_mouse_selection_copy(mouse, terminal->console);
+	}
+}
+
+static void handle_mouse_drawing(struct kmscon_mouse_info* mouse,
+								 struct kmscon_text* txt)
+{
+	if (!mouse || !txt)
+		return;
+
+	// draw mouse-cursor only if no buttons are pressed and hiding is off
+	if (kmscon_mouse_is_released(mouse, KMSCON_MOUSE_BUTTON_LEFT) &&
+		kmscon_mouse_is_released(mouse, KMSCON_MOUSE_BUTTON_MIDDLE) &&
+		kmscon_mouse_is_released(mouse, KMSCON_MOUSE_BUTTON_RIGHT) &&
+		!kmscon_mouse_is_hidden(mouse)) {
+		kmscon_text_render_pointer(txt,
+								   kmscon_mouse_get_x(mouse),
+								   kmscon_mouse_get_y(mouse));
+	}
+}
+
 static void do_redraw_screen(struct screen *scr)
 {
 	int ret;
@@ -141,6 +255,13 @@ static void do_redraw_screen(struct screen *scr)
 	tsm_screen_draw(scr->term->console, kmscon_text_draw_cb, scr->txt);
 	kmscon_text_render(scr->txt);
 
+	// deal with mapping normalized coords to character-cell coords
+	kmscon_mouse_set_mapping(scr->term->mouse, scr->disp, scr->txt);
+
+	handle_mouse_word_selection(scr->term->mouse,scr->txt, scr->term->console);
+	handle_mouse_random_selection(scr->term->mouse, scr->term);
+	handle_mouse_drawing(scr->term->mouse, scr->txt);
+
 	ret = uterm_display_swap(scr->disp, false);
 	if (ret) {
 		log_warning("cannot swap display %p", scr->disp);
@@ -681,6 +802,7 @@ int kmscon_terminal_register(struct kmscon_session **out,
 	term->ref = 1;
 	term->eloop = kmscon_seat_get_eloop(seat);
 	term->input = kmscon_seat_get_input(seat);
+	term->mouse = kmscon_seat_get_mouse(seat);
 	shl_dlist_init(&term->screens);
 
 	term->conf_ctx = kmscon_seat_get_conf(seat);
diff --git a/src/meson.build b/src/meson.build
index c18759f4..1a3c9cb4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -205,6 +205,8 @@ if enable_renderer_gltex
       'kmscon_mod_gltex.c',
       embed_gen.process('text_gltex_atlas.vert', extra_args: shader_regex),
       embed_gen.process('text_gltex_atlas.frag', extra_args: shader_regex),
+      embed_gen.process('mouse_pointer.vert', extra_args: shader_regex),
+      embed_gen.process('mouse_pointer.frag', extra_args: shader_regex),
     ],
     name_prefix: '',
     dependencies: [libtsm_deps, glesv2_deps, shl_deps],
@@ -249,6 +251,7 @@ kmscon_srcs = [
   'kmscon_seat.c',
   'kmscon_conf.c',
   'kmscon_main.c',
+  'kmscon_mouse.c',
 ]
 if enable_session_dummy
   kmscon_srcs += 'kmscon_dummy.c'
diff --git a/src/mouse_pointer.frag b/src/mouse_pointer.frag
new file mode 100644
index 00000000..847f01d3
--- /dev/null
+++ b/src/mouse_pointer.frag
@@ -0,0 +1,39 @@
+/*
+ * kmscon - Fragment Shader
+ *
+ * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
+ * Copyright (c) 2011 University of Tuebingen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Fragment Shader
+ * A basic fragment shader for drawing a simple mouse-pointer.
+ */
+
+precision mediump float;
+
+uniform vec4 color;
+
+void main()
+{
+   gl_FragColor = vec4(color);
+}
\ No newline at end of file
diff --git a/src/mouse_pointer.vert b/src/mouse_pointer.vert
new file mode 100644
index 00000000..50dac93a
--- /dev/null
+++ b/src/mouse_pointer.vert
@@ -0,0 +1,50 @@
+/*
+ * kmscon - Vertex Shader
+ *
+ * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
+ * Copyright (c) 2011 University of Tuebingen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Vertex Shader
+ * A basic vertex shader for drawing a simple mouse-pointer.
+ */
+
+uniform mat4 projection;
+uniform float orientation;
+uniform vec2 offset;
+
+attribute vec2 position;
+
+vec2 opRotate(in vec2 p, in float degrees)
+{
+    float rad = radians(degrees);
+    float c = cos(rad);
+    float s = sin(rad);
+    return p * mat2(vec2(c, s), vec2(-s, c));
+}
+
+void main()
+{
+   vec2 rotatedPosition = opRotate(position + offset, orientation);
+   gl_Position = projection * vec4(rotatedPosition, 0.0, 1.0);
+}
\ No newline at end of file
diff --git a/src/text.c b/src/text.c
index 9b116e27..ddbb77ec 100644
--- a/src/text.c
+++ b/src/text.c
@@ -489,6 +489,31 @@ int kmscon_text_render(struct kmscon_text *txt)
 	return ret;
 }
 
+/**
+ * kmscon_text_render_pointer:
+ * @txt: valid text renderer
+ * @cursor_x: column cell coordinate
+ * @cursor_y: row cell coordinate
+ *
+ * This draws a mouse-pointer cross-hair at the current mouse-position..
+ *
+ * Returns: 0 on success, negative error on failure.
+ */
+int kmscon_text_render_pointer(struct kmscon_text *txt,
+                              int cursor_x,
+                              int cursor_y)
+{
+   int ret = 0;
+
+   if (!txt)
+       return -EINVAL;
+
+   if (txt->ops->render_pointer)
+       ret = txt->ops->render_pointer(txt, cursor_x, cursor_y);
+
+   return ret;
+}
+
 /**
  * kmscon_text_abort:
  * @txt: valid text renderer
diff --git a/src/text.h b/src/text.h
index 269671e4..7a973613 100644
--- a/src/text.h
+++ b/src/text.h
@@ -84,6 +84,7 @@ struct kmscon_text_ops {
 		     unsigned int posx, unsigned int posy,
 		     const struct tsm_screen_attr *attr);
 	int (*render) (struct kmscon_text *txt);
+	int (*render_pointer) (struct kmscon_text *txt, int cursor_x, int cursor_y);
 	void (*abort) (struct kmscon_text *txt);
 };
 
@@ -112,6 +113,9 @@ int kmscon_text_draw(struct kmscon_text *txt,
 		     unsigned int posx, unsigned int posy,
 		     const struct tsm_screen_attr *attr);
 int kmscon_text_render(struct kmscon_text *txt);
+int kmscon_text_render_pointer(struct kmscon_text *txt,
+							   int cursor_x,
+							   int cursor_y);
 void kmscon_text_abort(struct kmscon_text *txt);
 
 int kmscon_text_draw_cb(struct tsm_screen *con,
diff --git a/src/text_gltex.c b/src/text_gltex.c
index d7d25136..5b219545 100644
--- a/src/text_gltex.c
+++ b/src/text_gltex.c
@@ -55,6 +55,8 @@
 #include "uterm_video.h"
 #include "text_gltex_atlas.frag.bin.h"
 #include "text_gltex_atlas.vert.bin.h"
+#include "mouse_pointer.frag.bin.h"
+#include "mouse_pointer.vert.bin.h"
 
 #define LOG_SUBSYSTEM "text_gltex"
 
@@ -94,6 +96,19 @@ struct glyph {
 #define GLYPH_STRIDE(gly) ((gly)->glyph->buf.stride)
 #define GLYPH_DATA(gly) ((gly)->glyph->buf.data)
 
+GLfloat mouse_block_vertices[] = {-.01f,  .02f,  // 0) top-left
+								   .01f,  .02f,  // 1) top-right
+								  -.01f, -.02f,  // 2) bottom-left
+								   .01f, -.02f}; // 3) bottom-right
+
+GLuint mouse_block_outline_indices[] = {0, 1,
+										1, 3,
+										0, 2,
+										2, 3};
+
+GLuint mouse_block_fill_indices[] = {0, 1, 3,
+									 0, 3, 2};
+
 struct gltex {
 	struct shl_hashtable *glyphs;
 	struct shl_hashtable *bold_glyphs;
@@ -116,6 +131,12 @@ struct gltex {
 	unsigned int sh;
 
 	GLfloat angle;
+
+	struct gl_shader *mouse_pointer_shader;
+	GLuint uni_orientation_mouse;
+	GLuint uni_proj_mouse;
+	GLuint uni_color_mouse;
+	GLuint uni_offset_mouse;
 };
 
 #define FONT_WIDTH(txt) ((txt)->font->attr.width)
@@ -203,6 +224,34 @@ static int gltex_set(struct kmscon_text *txt)
 		goto err_shader;
 	}
 
+	vert = _binary_mouse_pointer_vert_start;
+	vlen = _binary_mouse_pointer_vert_size;
+	frag = _binary_mouse_pointer_frag_start;
+	flen = _binary_mouse_pointer_frag_size;
+	gl_clear_error();
+	static char *mouse_pointer_attr[] = { "position" };
+	ret = gl_shader_new(&gt->mouse_pointer_shader,
+						vert, vlen,
+						frag, flen,
+						mouse_pointer_attr, 1,
+						log_llog, NULL);
+	if (ret)
+		goto err_shader;
+
+	gt->uni_orientation_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader,
+													  "orientation");
+	gt->uni_proj_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader,
+											   "projection");
+	gt->uni_color_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader,
+												"color");
+	gt->uni_offset_mouse = gl_shader_get_uniform(gt->mouse_pointer_shader,
+												 "offset");
+
+	if (gl_has_error(gt->mouse_pointer_shader)) {
+		log_warning("cannot create mouse-pointer shader");
+		goto err_mouse_pointer_shader;
+	}
+
 	mode = uterm_display_get_current(txt->disp);
 	gt->sw = uterm_mode_get_width(mode);
 	gt->sh = uterm_mode_get_height(mode);
@@ -233,6 +282,8 @@ static int gltex_set(struct kmscon_text *txt)
 
 	return 0;
 
+err_mouse_pointer_shader:
+	gl_shader_unref(gt->mouse_pointer_shader);
 err_shader:
 	gl_shader_unref(gt->shader);
 err_bold_htable:
@@ -750,6 +801,70 @@ static int gltex_render(struct kmscon_text *txt)
 	return 0;
 }
 
+static int gltex_render_pointer(struct kmscon_text *txt, int cursor_x, int cursor_y)
+{
+	struct gltex *gt = txt->data;
+	float mat[16];
+
+	gl_clear_error();
+
+	gl_shader_use(gt->mouse_pointer_shader);
+
+	GLfloat pixel_w = .0f;
+	GLfloat pixel_h = .0f;
+	GLfloat hw = .0f;
+	GLfloat hh = .0f;
+
+	if (txt->orientation == ORIENTATION_NORMAL || txt->orientation == ORIENTATION_INVERTED) {
+		pixel_w = 2.0f/gt->sw;
+		pixel_h = 2.0f/gt->sh;
+		hw = FONT_WIDTH(txt)*pixel_w*.5f;
+		hh = FONT_HEIGHT(txt)*pixel_h*.5f;
+	} else if (txt->orientation == ORIENTATION_RIGHT || txt->orientation == ORIENTATION_LEFT) {
+		pixel_w = 2.0f/gt->sh;
+		pixel_h = 2.0f/gt->sw;
+		hw = FONT_WIDTH(txt)*pixel_w*.5f;
+		hh = FONT_HEIGHT(txt)*pixel_h*.5f;
+	}
+
+	mouse_block_vertices[0] = -hw; // top-left
+	mouse_block_vertices[1] =  hh;
+	mouse_block_vertices[2] =  hw - pixel_w; // top-right
+	mouse_block_vertices[3] =  hh;
+	mouse_block_vertices[4] = -hw; // bottom-left
+	mouse_block_vertices[5] = -hh + pixel_h;
+	mouse_block_vertices[6] =  hw - pixel_w; // bottom-right
+	mouse_block_vertices[7] = -hh + pixel_h;
+
+	glViewport(0, 0, gt->sw, gt->sh);
+	glEnable(GL_BLEND);
+	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+	gl_m4_identity(mat);
+	glUniformMatrix4fv(gt->uni_proj_mouse, 1, GL_FALSE, mat);
+	glUniform1f(gt->uni_orientation_mouse, gt->angle);
+
+	GLfloat top_left[2] = {-1.f, 1.f};
+	GLfloat x = top_left[0] + hw + cursor_x*FONT_WIDTH(txt)*pixel_w;
+	GLfloat y = top_left[1] - hh - cursor_y*FONT_HEIGHT(txt)*pixel_h;
+	glUniform2f(gt->uni_offset_mouse, x, y);
+
+	glEnableVertexAttribArray(0);
+
+	// block-cursor outline
+	glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, mouse_block_vertices);
+	glUniform4f(gt->uni_color_mouse, 1.f, .5f, .25f, .9f);
+	glDrawElements(GL_LINES, 8, GL_UNSIGNED_INT, mouse_block_outline_indices);
+
+	// block-cursor fill
+	glUniform4f(gt->uni_color_mouse, 1.f, .5f, .25f, .35f);
+	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, mouse_block_fill_indices);
+
+	glDisableVertexAttribArray(0);
+
+	return 0;
+}
+
 struct kmscon_text_ops kmscon_text_gltex_ops = {
 	.name = "gltex",
 	.owner = NULL,
@@ -761,5 +876,6 @@ struct kmscon_text_ops kmscon_text_gltex_ops = {
 	.prepare = gltex_prepare,
 	.draw = gltex_draw,
 	.render = gltex_render,
+	.render_pointer = gltex_render_pointer,
 	.abort = NULL,
 };

From 7103379b42dd6e1f9feb2ecc1f084e2b625c4365 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mirco=20M=C3=BCller?= <macslow@gmail.com>
Date: Mon, 24 Jul 2023 16:18:31 +0200
Subject: [PATCH 3/4] Ported gyro-sensor support from my (MacSlow) old
 kmscon-upstream branch to aetf's meson-based develop branch.

---
 meson.build           |   1 +
 src/kmscon_terminal.c | 256 +++++++++++++++++++++++++++++++++++++++++-
 src/meson.build       |   2 +-
 3 files changed, 257 insertions(+), 2 deletions(-)

diff --git a/meson.build b/meson.build
index 3f4fce25..1750bb50 100644
--- a/meson.build
+++ b/meson.build
@@ -51,6 +51,7 @@ libtsm_deps = dependency('libtsm', version: '>=4.0.2')
 libudev_deps = dependency('libudev', version: '>=172')
 dl_deps = dependency('dl')
 threads_deps = dependency('threads')
+dbus_deps = dependency('dbus-1', version: '>=1.14.4')
 
 python = find_program('python3')
 
diff --git a/src/kmscon_terminal.c b/src/kmscon_terminal.c
index 30e1d9ac..9ffb6664 100644
--- a/src/kmscon_terminal.c
+++ b/src/kmscon_terminal.c
@@ -59,6 +59,16 @@
 
 #define LOG_SUBSYSTEM "terminal"
 
+#include <dbus/dbus.h>
+static const char* FDO_PROPS_INTERFACE = "org.freedesktop.DBus.Properties";
+static const char* FDO_GET_METHOD = "Get";
+static const char* GYRO_CLAIM_METHOD = "ClaimAccelerometer";
+static const char* GYRO_RELEASE_METHOD = "ReleaseAccelerometer";
+static const char* SENSOR_INTERFACE = "net.hadess.SensorProxy";
+static const char* DESTINATION = "net.hadess.SensorProxy";
+static const char* SENSOR_PATH = "/net/hadess/SensorProxy";
+static const char* PROPERTY_HAS_GYRO = "HasAccelerometer";
+
 struct screen {
 	struct shl_dlist list;
 	struct kmscon_terminal *term;
@@ -95,6 +105,12 @@ struct kmscon_terminal {
 
 	struct kmscon_mouse_info* mouse;
 	struct kmscon_selection_info* selection;
+
+	DBusError dbus_error;
+	DBusConnection* dbus_connection;
+	struct ev_timer* dbus_gyro_query_timer;
+	struct itimerspec dbus_gyro_query_timer_spec;
+	bool has_gyro;
 };
 
 static void terminal_resize(struct kmscon_terminal *term,
@@ -241,6 +257,184 @@ static void handle_mouse_drawing(struct kmscon_mouse_info* mouse,
 	}
 }
 
+DBusHandlerResult properties_changed_cb(DBusConnection* connection,
+										DBusMessage* message,
+										void* user_data)
+{
+	// ignore these
+	(void) connection;
+
+	struct kmscon_terminal* term = (struct kmscon_terminal*) user_data;
+	struct shl_dlist *iter;
+
+	const char* orientation = "undefined"; //ORIENTATION_UNDEFINED;
+
+	DBusError error;
+	dbus_error_init (&error);
+
+	const char* interface = dbus_message_get_interface (message);
+	const char* path = dbus_message_get_path (message);
+
+	if (!interface || !path ||
+		(strncmp (interface, FDO_PROPS_INTERFACE, 31) != 0 &&
+		 strncmp (path, SENSOR_PATH, 23) != 0)) {
+			dbus_error_free (&error);
+			return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	DBusMessageIter args;
+	if (message && !dbus_message_iter_init (message, &args)) {
+		dbus_error_free (&error);
+		return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+	}
+
+	char* value = NULL;
+	int type = 0;
+	DBusMessageIter dict;
+	DBusMessageIter entry;
+	DBusMessageIter variant;
+	while ((type = dbus_message_iter_get_arg_type (&args)) != DBUS_TYPE_INVALID) {
+		switch (type) {
+			case DBUS_TYPE_ARRAY:
+				dbus_message_iter_recurse (&args, &dict);
+				type = dbus_message_iter_get_arg_type (&dict);
+				if (type == DBUS_TYPE_DICT_ENTRY) {
+					dbus_message_iter_recurse (&dict, &entry);
+					while ((type = dbus_message_iter_get_arg_type (&entry)) != DBUS_TYPE_INVALID) {
+						type = dbus_message_iter_get_arg_type (&entry);
+						switch (type) {
+							case DBUS_TYPE_VARIANT:
+								dbus_message_iter_recurse (&entry, &variant);
+								type = dbus_message_iter_get_arg_type (&variant);
+								if (type == DBUS_TYPE_STRING) {
+									dbus_message_iter_get_basic (&variant, &value);
+									orientation = value;
+								}
+							break;
+
+							default: break;
+						}
+						dbus_message_iter_next (&entry);
+					}
+				}
+			break;
+
+			default : break;
+		}
+		dbus_message_iter_next (&args);
+	}
+
+	shl_dlist_for_each(iter, &term->screens) {
+		struct screen *scr = shl_dlist_entry(iter, struct screen, list);
+
+		if (strncmp(orientation, "normal", 6) == 0)
+			kmscon_text_rotate(scr->txt, ORIENTATION_NORMAL);
+
+		if (strncmp(orientation, "left-up", 7) == 0)
+			kmscon_text_rotate(scr->txt, ORIENTATION_LEFT);
+
+		if (strncmp(orientation, "right-up", 8) == 0)
+			kmscon_text_rotate(scr->txt, ORIENTATION_RIGHT);
+
+		if (strncmp(orientation, "bottom-up", 9) == 0)
+			kmscon_text_rotate(scr->txt, ORIENTATION_INVERTED);
+
+		term->min_cols = 0;
+		term->min_rows = 0;
+		terminal_resize(term,
+						kmscon_text_get_cols(scr->txt),
+						kmscon_text_get_rows(scr->txt),
+						true,
+						true);
+	}
+
+	log_info("kmscon_terminal... orientation: %s", orientation);
+
+	dbus_error_free (&error);
+
+	return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+DBusMessage* get_property (DBusConnection* connection,
+						   DBusError* error,
+						   const char* property)
+{
+	DBusMessage* message = dbus_message_new_method_call (DESTINATION,
+														 SENSOR_PATH,
+														 FDO_PROPS_INTERFACE,
+														 FDO_GET_METHOD);
+	if (!message) {
+		return NULL;
+	}
+
+	const char* interface = SENSOR_INTERFACE;
+	DBusMessageIter args;
+	dbus_message_iter_init_append (message, &args);
+	dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &interface);
+	dbus_message_iter_append_basic (&args, DBUS_TYPE_STRING, &property);
+
+	DBusMessage* reply = dbus_connection_send_with_reply_and_block (connection,
+																	message,
+																	-1,
+																	error);
+	dbus_message_unref (message);
+
+	return reply;
+}
+
+dbus_bool_t has_gyro (DBusConnection* connection, DBusError* error)
+{
+	DBusMessage* reply = get_property (connection, error, PROPERTY_HAS_GYRO);
+
+	if (!reply) {
+		log_error("dbus-message is NULL!");
+		return 0;
+	}
+
+	if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_ERROR) {
+		log_error("%s", dbus_message_get_error_name (reply));
+		return 0;
+	}
+
+	DBusMessageIter reply_args;
+	dbus_message_iter_init (reply, &reply_args);
+	DBusMessageIter variant_iter;
+	dbus_message_iter_recurse (&reply_args, &variant_iter);
+	dbus_bool_t accelerometer;
+	dbus_message_iter_get_basic (&variant_iter, &accelerometer);
+	dbus_message_unref (reply);
+
+	return accelerometer;
+}
+
+void call_method (DBusConnection* connection, const char* method)
+{
+	DBusMessage* message = dbus_message_new_method_call (DESTINATION,
+														 SENSOR_PATH,
+														 SENSOR_INTERFACE,
+														 method);
+
+	dbus_uint32_t serial;
+	dbus_bool_t success = dbus_connection_send (connection, message, &serial);
+
+	if (!success) {
+		log_info("There was an error with the message call '%s'", method);
+	}
+
+	dbus_message_unref (message);
+}
+
+void dbus_gyro_query_timer_cb(struct ev_timer *timer, uint64_t num, void *data)
+{
+	if (!data) {
+		log_warn(" No valid pointer passed to dbus_gyro_query_timer_cb().");
+		return;
+	}
+
+	DBusConnection* dbus_connection = (DBusConnection*) data;
+	dbus_connection_read_write_dispatch (dbus_connection, 1);
+}
+
 static void do_redraw_screen(struct screen *scr)
 {
 	int ret;
@@ -721,6 +915,9 @@ static void terminal_destroy(struct kmscon_terminal *term)
 	tsm_screen_unref(term->console);
 	uterm_input_unref(term->input);
 	ev_eloop_unref(term->eloop);
+	call_method (term->dbus_connection, GYRO_RELEASE_METHOD);
+	dbus_error_free (&term->dbus_error);
+	dbus_connection_unref (term->dbus_connection);
 	free(term);
 }
 
@@ -879,10 +1076,64 @@ int kmscon_terminal_register(struct kmscon_session **out,
 	ret = kmscon_seat_register_session(seat, &term->session, session_event,
 					   term);
 	if (ret) {
-		log_error("cannot register session for terminal: %d", ret);
+		log_error("Cannot register session for terminal: %d", ret);
 		goto err_input;
 	}
 
+	dbus_error_init (&term->dbus_error);
+	term->dbus_connection = dbus_bus_get (DBUS_BUS_SYSTEM, &term->dbus_error);
+
+	if (has_gyro (term->dbus_connection, &term->dbus_error)) {
+		term->has_gyro = true;
+		log_info("This system has a gyro-sensor");
+		call_method (term->dbus_connection, GYRO_CLAIM_METHOD);
+	} else {
+		term->has_gyro = false;
+		log_info("This system has NO gyro-sensor");
+	}
+
+	if (term->has_gyro) {
+		dbus_bus_add_match (term->dbus_connection,
+							"type='signal',\
+							interface='org.freedesktop.DBus.Properties',\
+							member='PropertiesChanged',\
+							sender='net.hadess.SensorProxy'",
+							&term->dbus_error);
+
+		dbus_bool_t result = dbus_connection_add_filter(term->dbus_connection,
+														properties_changed_cb,
+														term,
+														NULL);
+
+		if (!result) {
+			log_info("Failed to add filter to connection");
+		}
+
+		// dbus-gyro-query timer
+		term->dbus_gyro_query_timer_spec.it_interval.tv_sec  = 0;
+		term->dbus_gyro_query_timer_spec.it_interval.tv_nsec = 200*1000*1000;
+		term->dbus_gyro_query_timer_spec.it_value.tv_sec  = 0;
+		term->dbus_gyro_query_timer_spec.it_value.tv_nsec = 200*1000*1000;
+
+		ret = ev_timer_new (&term->dbus_gyro_query_timer,
+							&term->dbus_gyro_query_timer_spec,
+							dbus_gyro_query_timer_cb,
+							term->dbus_connection,
+							NULL,
+							NULL);
+		if (ret) {
+			log_error("Cannot create dbus-gyro-query timer: %d", ret);
+			goto err_free;
+		}
+		ev_timer_enable(term->dbus_gyro_query_timer);
+
+		ret = ev_eloop_add_timer(term->eloop, term->dbus_gyro_query_timer);
+		if (ret) {
+			log_error("Cannot add dbus-gyro-query timer to event-loop: %d", ret);
+			goto err_free;
+		}
+	}
+
 	ev_eloop_ref(term->eloop);
 	uterm_input_ref(term->input);
 	*out = term->session;
@@ -903,6 +1154,9 @@ int kmscon_terminal_register(struct kmscon_session **out,
 err_con:
 	tsm_screen_unref(term->console);
 err_free:
+	call_method (term->dbus_connection, GYRO_RELEASE_METHOD);
+	dbus_error_free (&term->dbus_error);
+	dbus_connection_unref (term->dbus_connection);
 	free(term);
 	return ret;
 }
diff --git a/src/meson.build b/src/meson.build
index 1a3c9cb4..244cea41 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -260,7 +260,7 @@ if enable_session_terminal
   kmscon_srcs += 'kmscon_terminal.c'
 endif
 kmscon = executable('kmscon', kmscon_srcs,
-  dependencies: [xkbcommon_deps, libtsm_deps, threads_deps, dl_deps, conf_deps, shl_deps, eloop_deps, uterm_deps],
+  dependencies: [xkbcommon_deps, libtsm_deps, threads_deps, dbus_deps, dl_deps, conf_deps, shl_deps, eloop_deps, uterm_deps],
   export_dynamic: true,
   install: true,
   install_dir: libexecdir,

From 7394629f2bb3916e365442938accf3b2018bf818 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mirco=20M=C3=BCller?= <macslow@gmail.com>
Date: Mon, 24 Jul 2023 16:37:17 +0200
Subject: [PATCH 4/4] Add a man-page for the kmscon.conf file.

---
 docs/man/kmscon.conf.1.xml.in | 443 ++++++++++++++++++++++++++++++++++
 docs/meson.build              |   1 +
 2 files changed, 444 insertions(+)
 create mode 100644 docs/man/kmscon.conf.1.xml.in

diff --git a/docs/man/kmscon.conf.1.xml.in b/docs/man/kmscon.conf.1.xml.in
new file mode 100644
index 00000000..c4f7cb20
--- /dev/null
+++ b/docs/man/kmscon.conf.1.xml.in
@@ -0,0 +1,443 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+          "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+  Written 2023 by Mirco "MacSlow" Müller <macslow@gmail.com>
+  Dedicated to the Public Domain
+-->
+
+<refentry id="kmscon.conf">
+  <refentryinfo>
+    <title>kmscon.conf</title>
+    <productname>kmscon.conf</productname>
+    <date>January 2023</date>
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Mirco</firstname>
+        <surname>Müller</surname>
+        <email>macslow@gmail.com</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>kmscon.conf</refentrytitle>
+    <manvolnum>1</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>kmscon.conf</refname>
+    <refpurpose>Configuration file for KMS/DRM based System Console</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>kmscon.conf is the configuration file to control the behavior of kmscon
+          and adjust it to your system-setup. It allows to remap the bindings of
+          keyboard-shortcuts, define the desired keyboard-layout, select font
+          attributes for text-rendering, hardware-accelerationn, orientation of
+          output and much more.</para>
+
+    <para>Below is a complete list of all recognized <emphasis>Options</emphasis>, their meaning and
+          possible values. In section <emphasis>Example</emphasis> is a typical real-world sample of
+          a configuration to guide you in creating your own.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Example</title>
+    <para>Here is a real-world example of a typical kmscon.conf file:</para>
+    <programlisting>
+### General Options ###
+verbose
+
+### Seat Options ###
+vt=1
+switchvt
+
+### Session Options ###
+session-max=6
+session-control
+
+### Terminal Options ###
+term=linux
+
+### Input Options ###
+xkb-model=pc102
+xkb-layout=de
+xkb-repeat-delay=200
+xkb-repeat-rate=65
+
+### Video Options ###
+drm
+hwaccel
+gpus=all
+render-engine=gltex
+rotate=normal
+
+### Font Options ###
+font-engine=pango
+font-size=18
+font-name=Ubuntu Mono
+    </programlisting>
+    <para>Any line starting with a #-character is ignored and considered to be a comment.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <para>Below is a complete list of all recognized options, their meaning and
+          possible values.</para>
+
+    <variablelist>
+      <para><emphasis>### General Options ###</emphasis></para>
+      <varlistentry>
+        <term><option>verbose</option></term>
+        <listitem>
+          <para>Make kmscon be very chatty about what it is doing. It prints to
+                stdout unless redirected. Off if not present in kmsconf.conf or
+                commented out. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>debug</option></term>
+        <listitem>
+          <para>Let kmscon be even more chatty. The text-output goes to stdout or
+                any file it was redirected to. Off if not present in kmsconf.conf
+                or commented out. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>silent</option></term>
+        <listitem>
+          <para>Suppress notices and warnings. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>configdir</option></term>
+        <listitem>
+          <para>Path to config directory. (default: /etc/kmscon)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>listen</option></term>
+        <listitem>
+          <para>Listen for new seats and spawn sessions accordingly. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Seat Options ###</emphasis></para>
+      <varlistentry>
+        <term><option>vt</option></term>
+        <listitem>
+          <para>Select which VT to run on. (default: auto)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>switchvt</option></term>
+        <listitem>
+          <para>Automatically switch to VT. (default: on)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>seats</option></term>
+        <listitem>
+          <para>Select seats to run on. (default: current)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Session Options ###</emphasis></para>
+
+      <varlistentry>
+        <term><option>session-max</option></term>
+        <listitem>
+          <para>Maximum number of sessions. (default: 50)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>session-control</option></term>
+        <listitem>
+          <para>Allow keyboard session-control. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>terminal-session</option></term>
+        <listitem>
+          <para>Enable terminal session. (default: on)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Terminal Options ###</emphasis></para>
+
+      <varlistentry>
+        <term><option>login</option></term>
+        <listitem>
+          <para>Start the given login process instead of the default process; all arguments following '--' will be be parsed as argv to this process. No more options after '--' will be parsed so use it at the end of the argument string. (default: /bin/login -p)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>term</option></term>
+        <listitem>
+          <para>Value of the TERM environment variable for the child process. (default: xterm-256color)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>reset-env</option></term>
+        <listitem>
+          <para>Reset environment before running child process. (default: on)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>palette</option></term>
+        <listitem>
+          <para>Select the used color palette. (default: default)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>sb-size</option></term>
+        <listitem>
+          <para>Size of the scrollback-buffer in lines. (default: 1000)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Input Options ###</emphasis></para>
+      <varlistentry>
+        <term><option>xkb-model</option></term>
+        <listitem>
+          <para>Set XkbModel for input devices. (default: unset)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>xkb-layout</option></term>
+        <listitem>
+          <para>Set XkbLayout for input devices. (default: unset)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>xkb-variant</option></term>
+        <listitem>
+          <para>Set XkbVariant for input devices. (default: unset)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>xkb-options</option></term>
+        <listitem>
+          <para>Set XkbOptions for input devices. (default: unset)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>xkb-keymap</option></term>
+        <listitem>
+          <para>Use a predefined keymap for input devices. (default: unset)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>xkb-repeat-delay</option></term>
+        <listitem>
+          <para>Initial delay for key-repeat in ms. (default: 250)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>xkb-repeat-rate</option></term>
+        <listitem>
+          <para>Delay between two key-repeats in ms. (default: 50)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Grabs/Keyboard-Shortcuts ###</emphasis></para>
+      <varlistentry>
+        <term><option>grab-scroll-up</option></term>
+        <listitem>
+          <para>Shortcut to scroll up. (default: &lt;Shift&gt;Up)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-scroll-down</option></term>
+        <listitem>
+          <para>Shortcut to scroll down. (default: &lt;Shift&gt;Down)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-page-up</option></term>
+        <listitem>
+          <para>Shortcut to scroll page up. (default: &lt;Shift&gt;Prior)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-page-down</option></term>
+        <listitem>
+          <para>Shortcut to scroll page down. (default: &lt;Shift&gt;Next)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-zoom-in</option></term>
+        <listitem>
+          <para>Shortcut to increase font size. (default: &lt;Ctrl&gt;Plus)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-zoom-out</option></term>
+        <listitem>
+          <para>Shortcut to decrease font size. (default: &lt;Ctrl&gt;Minus)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-session-next</option></term>
+        <listitem>
+          <para>Switch to next session. (default: &lt;Ctrl&gt;&lt;Logo&gt;Right)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-session-prev</option></term>
+        <listitem>
+          <para>Switch to previous session. (default: &lt;Ctrl&gt;&lt;Logo&gt;Left)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-session-dummy</option></term>
+        <listitem>
+          <para>Switch to dummy session. (default: &lt;Ctrl&gt;&lt;Logo&gt;Escape)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-session-close</option></term>
+        <listitem>
+          <para>Close current session. (default: &lt;Ctrl&gt;&lt;Logo&gt;BackSpace)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-terminal-new</option></term>
+        <listitem>
+          <para>Create new terminal session. (default: &lt;Ctrl&gt;&lt;Logo&gt;Return)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-rotate-cw</option></term>
+        <listitem>
+          <para>Rotate output clock-wise. (default: &lt;Logo&gt;Plus)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>grab-rotate-ccw</option></term>
+        <listitem>
+          <para>Rotate output counter-clock-wise. (default: &lt;Logo&gt;Minus)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Video Options ###</emphasis></para>
+      <varlistentry>
+        <term><option>drm</option></term>
+        <listitem>
+          <para>Use DRM if available. (default: on)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>hwaccel</option></term>
+        <listitem>
+          <para>Use 3D hardware-acceleration if available. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>gpus</option></term>
+        <listitem>
+          <para>GPU selection mode. (default: all)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>render-engine</option></term>
+        <listitem>
+          <para>Console renderer. (default: not set)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>render-timing</option></term>
+        <listitem>
+          <para>Print renderer timing information. (default: off)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>rotate</option></term>
+        <listitem>
+          <para>Orientation of output to use. (default: normal)</para>
+        </listitem>
+      </varlistentry>
+
+      <para><emphasis>### Font Options ###</emphasis></para>
+      <varlistentry>
+        <term><option>font-engine</option></term>
+        <listitem>
+          <para>Font engine to use. (default: pango)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>font-size</option></term>
+        <listitem>
+          <para>Font size in points. (default: 12)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>font-name</option></term>
+        <listitem>
+          <para>Font name to use. (default: monospace)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>font-dpi</option></term>
+        <listitem>
+          <para>Force DPI value for all fonts. (default: 96)</para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Files</title>
+    <para><emphasis>/etc/kmsconf.conf</emphasis></para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>kmscon</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+</refentry>
diff --git a/docs/meson.build b/docs/meson.build
index a26e520f..cf33578c 100644
--- a/docs/meson.build
+++ b/docs/meson.build
@@ -4,6 +4,7 @@
 
 manpages = [
   'kmscon.1.xml.in',
+  'kmscon.conf.1.xml.in',
 ]
 
 data = configuration_data()
openSUSE Build Service is sponsored by