[PATCH] clients: wlterm: A TSM based terminal emulator

David Herrmann dh.herrmann at googlemail.com
Wed Sep 19 04:47:25 PDT 2012


TSM (Terminal-emulator State Machine) is a library developed together with
"kmscon" which implements a terminal-emulation layer. It has no external
depedencies and thus does not do any rendering.
It was solely developed to allow writing terminal emulators for different
platforms but still providing the same environment for applications
running in it. It is similar to libvte but without the huge GTK
dependency.

TSM is developed in the kmscon repository (github.com/dvdhrm/kmscon) but I
created a single header file containing the whole library. As it is 6k
lines long, you need to get it here: https://gist.github.com/3749173
Simply save the file as ./clients/tsm.h before applying this patch. You
can also install the TSM library from the kmscon repository and simply
adjust the include-line:
   from: #include "tsm.h"
     to: #include <tsm_vte.h>

The terminal emulator itself is pretty simple and slightly based on the
exisiting weston-terminal code.

This code is just an example how TSM can be used to write a simple
terminal emulator. I don't want this to be applied to the repository. But
I will improve this code and resend a patch with an optional TSM
dependency so wlterm can be built from the weston-repository.

The code base is still under heavy development so this is just some
preview and it will take a while until this is finished.

Signed-off-by: David Herrmann <dh.herrmann at googlemail.com>
---
 clients/.gitignore  |   1 +
 clients/Makefile.am |   4 +
 clients/wlterm.c    | 464 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 469 insertions(+)
 create mode 100644 clients/wlterm.c

diff --git a/clients/.gitignore b/clients/.gitignore
index 6ed849d..29c8765 100644
--- a/clients/.gitignore
+++ b/clients/.gitignore
@@ -32,5 +32,6 @@ weston-screensaver
 weston-screenshooter
 weston-tablet-shell
 weston-terminal
+wlterm
 workspaces-client-protocol.h
 workspaces-protocol.c
diff --git a/clients/Makefile.am b/clients/Makefile.am
index 85fc95c..1b05fc9 100644
--- a/clients/Makefile.am
+++ b/clients/Makefile.am
@@ -56,6 +56,7 @@ clients_programs =				\
 	clickdot				\
 	editor					\
 	keyboard				\
+	wlterm					\
 	$(full_gl_client_programs)
 
 desktop_shell = weston-desktop-shell
@@ -122,6 +123,9 @@ keyboard_SOURCES = 				\
 	input-method-client-protocol.h
 keyboard_LDADD = $(toolkit_libs)
 
+wlterm_SOURCES = wlterm.c
+wlterm_LDADD = $(toolkit_libs) -lutil
+
 weston_info_SOURCES =				\
 	weston-info.c				\
 	../shared/os-compatibility.c		\
diff --git a/clients/wlterm.c b/clients/wlterm.c
new file mode 100644
index 0000000..7816eb0
--- /dev/null
+++ b/clients/wlterm.c
@@ -0,0 +1,464 @@
+/*
+ * wlterm - Wayland Terminal
+ *
+ * Copyright (c) 2011-2012 David Herrmann <dh.herrmann at googlemail.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.
+ */
+
+/*
+ * This is based on the "weston-terminal" code from:
+ *
+ * Copyright 2008 Kristian Hoegsberg
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting documentation, and
+ * that the name of the copyright holders not be used in advertising or
+ * publicity pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no representations
+ * about the suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
+ * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, 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.
+ */
+
+#include <cairo.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pty.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <unistd.h>
+#include <wayland-client.h>
+#include "../shared/config-parser.h"
+#include "window.h"
+#include "tsm.h"
+
+struct wlterm {
+	struct display *disp;
+	struct window *wnd;
+	struct widget *wid;
+	int margin;
+
+	struct tsm_screen *scr;
+	struct tsm_vte *vte;
+
+	int master;
+	struct task io_task;
+
+	cairo_scaled_font_t *font;
+	cairo_font_extents_t font_extents;
+};
+
+static void wlterm_destroy(struct wlterm *term);
+
+static void key_handler(struct window *window, struct input *input,
+			uint32_t time, uint32_t key, uint32_t sym,
+			enum wl_keyboard_key_state state, void *data)
+{
+	struct wlterm *term = data;
+	uint32_t modifiers, ucs4;
+	unsigned int mods;
+
+	if (state != WL_KEYBOARD_KEY_STATE_PRESSED)
+		return;
+
+	modifiers = input_get_modifiers(input);
+	mods = 0;
+	if (modifiers & MOD_CONTROL_MASK)
+		mods |= TSM_CONTROL_MASK;
+	if (modifiers & MOD_SHIFT_MASK)
+		mods |= TSM_SHIFT_MASK;
+	if (modifiers & MOD_ALT_MASK)
+		mods |= TSM_MOD1_MASK;
+
+	ucs4 = xkb_keysym_to_utf32(sym) ? : TSM_VTE_INVALID;
+
+	if (tsm_vte_handle_keyboard(term->vte, sym, mods, ucs4))
+		window_schedule_redraw(term->wnd);
+}
+
+static void keyboard_focus_handler(struct window *window,
+				   struct input *device, void *data)
+{
+	struct wlterm *term = data;
+
+	window_schedule_redraw(term->wnd);
+}
+
+struct wldraw {
+	struct wlterm *term;
+	cairo_t *cr;
+	unsigned int x_adv;
+	unsigned int y_adv;
+	unsigned int x_off;
+	unsigned int y_off;
+	int ascent;
+};
+
+static int draw_cell(struct tsm_screen *scr,
+		     uint32_t id,
+		     const uint32_t *ch,
+		     size_t ulen,
+		     unsigned int posx,
+		     unsigned int posy,
+		     const struct tsm_screen_attr *attr,
+		     void *data)
+{
+	struct wldraw *d = data;
+	struct wlterm *term = d->term;
+	unsigned int x, y;
+	cairo_t *cr = d->cr;
+	char buf[4];
+	size_t len;
+	cairo_glyph_t glyphs[8], *g = glyphs;
+	int num_glyphs = 8;
+
+	x = d->x_adv * posx + d->x_off;
+	y = d->y_adv * posy + d->y_off;
+	cairo_move_to(cr, x, y);
+
+	len = tsm_ucs4_to_utf8(*ch, buf);
+
+	if (attr->inverse) {
+		cairo_set_source_rgb(cr,
+				     attr->fr / 255.0,
+				     attr->fg / 255.0,
+				     attr->fb / 255.0);
+		cairo_rectangle(cr, x, y, d->x_adv, d->y_adv);
+		cairo_fill(cr);
+		cairo_set_source_rgb(cr,
+				     attr->br / 255.0,
+				     attr->bg / 255.0,
+				     attr->bb / 255.0);
+	} else {
+		cairo_set_source_rgb(cr,
+				     attr->br / 255.0,
+				     attr->bg / 255.0,
+				     attr->bb / 255.0);
+		cairo_rectangle(cr, x, y, d->x_adv, d->y_adv);
+		cairo_fill(cr);
+		cairo_set_source_rgb(cr,
+				     attr->fr / 255.0,
+				     attr->fg / 255.0,
+				     attr->fb / 255.0);
+	}
+
+	cairo_scaled_font_text_to_glyphs(term->font, x, y + d->ascent,
+					 buf, len,
+					 &g, &num_glyphs,
+					 NULL, NULL, NULL);
+	cairo_show_glyphs(cr, glyphs, num_glyphs);
+
+	return 0;
+}
+
+static void redraw_handler(struct widget *widget, void *data)
+{
+	struct wlterm *term = data;
+	cairo_surface_t *surface;
+	cairo_t *cr;
+	struct rectangle alloc;
+	int side_margin, top_margin;
+	unsigned int cols, rows, xad, yad;
+	struct wldraw d;
+
+	/* get terminal data */
+	surface = window_get_surface(term->wnd);
+	cr = cairo_create(surface);
+	widget_get_allocation(term->wid, &alloc);
+	xad = term->font_extents.max_x_advance;
+	yad = term->font_extents.height;
+	cols = tsm_screen_get_width(term->scr);
+	rows = tsm_screen_get_height(term->scr);
+
+	/* draw global background */
+	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+	cairo_set_source_rgb(cr, 0, 0, 0);
+	cairo_rectangle(cr, alloc.x, alloc.y, alloc.width, alloc.height);
+	cairo_fill(cr);
+
+	/* prepare brush */
+	cairo_set_scaled_font(cr, term->font);
+	side_margin = (alloc.width - cols * xad) / 2;
+	top_margin = (alloc.height - rows * yad) / 2;
+	cairo_set_source_rgba(cr, 1, 1, 1, 1);
+	cairo_set_line_width(cr, 1.0);
+
+	/* paint glyph foreground */
+	memset(&d, 0, sizeof(d));
+	d.term = term;
+	d.cr = cr;
+	d.x_adv = xad;
+	d.y_adv = yad;
+	d.x_off = alloc.x + side_margin;
+	d.y_off = alloc.y + top_margin;
+	d.ascent = term->font_extents.ascent;
+	tsm_screen_draw(term->scr, NULL, draw_cell, NULL, &d);
+
+	/* render and cleanup */
+	cairo_destroy(cr);
+	cairo_surface_destroy(surface);
+}
+
+static void wlterm_resize(struct wlterm *term, unsigned int cols,
+			  unsigned int rows)
+{
+	struct winsize ws;
+
+	tsm_screen_resize(term->scr, cols, rows);
+
+	memset(&ws, 0, sizeof(ws));
+	ws.ws_col = cols;
+	ws.ws_row = rows;
+	ioctl(term->master, TIOCSWINSZ, &ws);
+}
+
+static void resize_handler(struct widget *widget,
+			   int32_t width, int32_t height, void *data)
+{
+	struct wlterm *term = data;
+	int32_t cols, rows, m;
+
+	m = 2 * term->margin;
+	cols = (width - m) / (int32_t) term->font_extents.max_x_advance;
+	rows = (height - m) / (int32_t) term->font_extents.height;
+
+	wlterm_resize(term, cols, rows);
+}
+
+static void vte_event(struct tsm_vte *vte, const char *u8, size_t len,
+		      void *data)
+{
+	struct wlterm *term = data;
+
+	if (write(term->master, u8, len) < 0) {
+		/* TODO: this is nonblocking so avoid abort() here! */
+		abort();
+	}
+}
+
+static void
+io_handler(struct task *task, uint32_t events)
+{
+	struct wlterm *term = container_of(task, struct wlterm, io_task);
+	char buffer[256];
+	int len;
+
+	if (events & EPOLLHUP) {
+		fprintf(stderr, "pty fd HUP (%d): %m\n", errno);
+		/* TODO: exit */
+		return;
+	}
+
+	len = read(term->master, buffer, sizeof(buffer));
+	if (len < 0) {
+		fprintf(stderr, "pty fd error (%d): %m\n", errno);
+		/* TODO: exit */
+	} else {
+		tsm_vte_input(term->vte, buffer, len);
+		window_schedule_redraw(term->wnd);
+	}
+}
+
+static int pty_start(struct wlterm *term, const char *path)
+{
+	int master;
+	pid_t pid;
+
+	pid = forkpty(&master, NULL, NULL, NULL);
+	if (pid == 0) {
+		setenv("TERM", "xterm-256color", 1);
+		setenv("COLORTERM", "xterm-256color", 1);
+		execl(path, path, NULL);
+		fprintf(stderr, "exec failed: %m\n");
+		exit(EXIT_FAILURE);
+	} else if (pid < 0) {
+		fprintf(stderr, "failed to fork and create pty (%d): %m\n",
+			errno);
+		return -errno;
+	}
+
+	term->master = master;
+	fcntl(master, F_SETFL, O_NONBLOCK);
+	term->io_task.run = io_handler;
+	display_watch_fd(term->disp, term->master,
+			 EPOLLIN | EPOLLHUP, &term->io_task);
+	wlterm_resize(term, 80, 24);
+
+	return 0;
+}
+
+static void pty_close(struct wlterm *term)
+{
+	close(term->master);
+}
+
+static int wlterm_create(struct wlterm **out, struct display *disp)
+{
+	struct wlterm *term;
+	int ret;
+	cairo_surface_t *surface;
+	cairo_t *cr;
+
+	if (!out || !disp)
+		return -EINVAL;
+
+	term = malloc(sizeof(*term));
+	if (!term)
+		return -ENOMEM;
+	memset(term, 0, sizeof(*term));
+	term->disp = disp;
+	term->margin = 5;
+
+	term->wnd = window_create(term->disp);
+	if (!term->wnd) {
+		fprintf(stderr, "cannot create window (%d): %m\n",
+			errno);
+		ret = -EFAULT;
+		goto err_free;
+	}
+
+	term->wid = frame_create(term->wnd, term);
+	if (!term->wid) {
+		fprintf(stderr, "cannot create widget (%d): %m\n",
+			errno);
+		ret = -EFAULT;
+		goto err_wnd;
+	}
+	widget_set_transparent(term->wid, 0);
+
+	ret = tsm_screen_new(&term->scr, NULL);
+	if (ret) {
+		fprintf(stderr, "cannot create TSM screen: %d\n", ret);
+		goto err_wid;
+	}
+
+	ret = tsm_vte_new(&term->vte, term->scr, vte_event, term, NULL);
+	if (ret) {
+		fprintf(stderr, "cannot create TSM VTE machine: %d\n", ret);
+		goto err_scr;
+	}
+
+	ret = pty_start(term, "/bin/bash");
+	if (ret)
+		goto err_vte;
+
+	/* get (toy) font */
+	surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
+	cr = cairo_create(surface);
+	cairo_set_font_size(cr, 14);
+	cairo_select_font_face(cr, "Monospace",
+			       CAIRO_FONT_SLANT_NORMAL,
+			       CAIRO_FONT_WEIGHT_NORMAL);
+	term->font = cairo_get_scaled_font(cr);
+	cairo_scaled_font_reference(term->font);
+	cairo_font_extents(cr, &term->font_extents);
+	cairo_destroy(cr);
+	cairo_surface_destroy(surface);
+
+	/* set TSM options */
+	tsm_screen_set_max_sb(term->scr, 2000);
+
+	/* set Window options */
+	window_set_user_data(term->wnd, term);
+	window_set_title(term->wnd, "Wayland Terminal");
+	window_set_key_handler(term->wnd, key_handler);
+	window_set_keyboard_focus_handler(term->wnd,
+					  keyboard_focus_handler);
+
+	/* set widget options */
+	widget_set_redraw_handler(term->wid, redraw_handler);
+	widget_set_resize_handler(term->wid, resize_handler);
+	widget_schedule_resize(term->wid, 400, 400);
+
+	*out = term;
+	return 0;
+
+err_vte:
+	tsm_vte_unref(term->vte);
+err_scr:
+	tsm_screen_unref(term->scr);
+err_wid:
+	widget_destroy(term->wid);
+err_wnd:
+	window_destroy(term->wnd);
+err_free:
+	free(term);
+	return ret;
+}
+
+static void wlterm_destroy(struct wlterm *term)
+{
+	if (!term)
+		return;
+
+	cairo_scaled_font_destroy(term->font);
+	pty_close(term);
+	tsm_vte_unref(term->vte);
+	tsm_screen_unref(term->scr);
+	widget_destroy(term->wid);
+	window_destroy(term->wnd);
+	free(term);
+}
+
+int main(int argc, char **argv)
+{
+	struct display *display;
+	struct wlterm *term;
+	int ret;
+
+	display = display_create(argc, argv);
+	if (!display) {
+		fprintf(stderr, "cannot create display (%d): %m\n",
+			errno);
+		ret = -EFAULT;
+		goto err_out;
+	}
+
+	ret = wlterm_create(&term, display);
+	if (ret)
+		goto err_display;
+
+	display_run(display);
+	ret = 0;
+
+	wlterm_destroy(term);
+err_display:
+	display_destroy(display);
+err_out:
+	ret = abs(ret);
+	if (ret)
+		fprintf(stderr, "application failed with (%d): %s\n",
+			ret, strerror(ret));
+	return ret;
+}
-- 
1.7.12



More information about the wayland-devel mailing list