[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