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

Kristian Høgsberg hoegsberg at gmail.com
Tue Sep 25 07:37:53 PDT 2012


On Wed, Sep 19, 2012 at 01:47:25PM +0200, David Herrmann wrote:
> 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>

Sounds very useful, will be interesting to see where this goes.  I was
hoping to find something like tsm.h when I originally did
weston-terminal, so if this works out well, we could consider just
making wlterm the wayland terminal.

Kristian

> 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