summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2014-04-24 18:15:29 +0200
committerDavid Herrmann <dh.herrmann@gmail.com>2014-04-24 18:15:29 +0200
commitb73acb4c71698a764763ae8dad94c1e8a2b8d7a3 (patch)
tree1c97e385dfce786fd2b344deee70e4c7392df245
parentf9e64d16390ef558ca2865d9ea450eb3510ec6da (diff)
gtktsm: add libtsm exampleHEADmaster
GtkTsmTerminal is a Gtk+-3.0 widget using libtsm. It provides a generic terminal-widget that can be easily used to create any kinds of terminal-emulators. The GtkTsm program is a small example that shows how to use GtkTsm. Note that all this is optional and only meant as example. Maybe at some point we will install GtkTsmTerminal as shared object. However, it will never be mandatory! Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am30
-rw-r--r--configure.ac34
-rw-r--r--src/gtktsm/gtktsm-app.c200
-rw-r--r--src/gtktsm/gtktsm-app.h43
-rw-r--r--src/gtktsm/gtktsm-terminal.c1996
-rw-r--r--src/gtktsm/gtktsm-terminal.h77
-rw-r--r--src/gtktsm/gtktsm-win.c152
-rw-r--r--src/gtktsm/gtktsm-win.h50
-rw-r--r--src/gtktsm/gtktsm.c44
10 files changed, 2627 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 623c492..4dfe640 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,6 +20,7 @@ config.h.in~
config.log
config.status
configure
+/gtktsm
libtool
m4/
src/tsm/libtsm.pc
diff --git a/Makefile.am b/Makefile.am
index 70d159b..f12bdf9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,6 +40,7 @@ TPHONY =
TESTS =
MEMTESTS =
check_PROGRAMS =
+bin_PROGRAMS =
lib_LTLIBRARIES =
noinst_LTLIBRARIES =
@@ -169,6 +170,35 @@ libtsm_test_la_CPPFLAGS += $(XKBCOMMON_CFLAGS)
endif
#
+# GtkTsm - Example
+#
+
+if BUILD_ENABLE_GTKTSM
+bin_PROGRAMS += gtktsm
+endif
+
+gtktsm_SOURCES = \
+ src/gtktsm/gtktsm.c \
+ src/gtktsm/gtktsm-app.h \
+ src/gtktsm/gtktsm-app.c \
+ src/gtktsm/gtktsm-terminal.h \
+ src/gtktsm/gtktsm-terminal.c \
+ src/gtktsm/gtktsm-win.h \
+ src/gtktsm/gtktsm-win.c \
+ src/shared/shl-pty.h \
+ src/shared/shl-pty.c
+gtktsm_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(GTKTSM_CFLAGS)
+gtktsm_LDADD = \
+ -lm \
+ libshl.la \
+ libtsm.la \
+ $(GTKTSM_LIBS)
+gtktsm_LDFLAGS = \
+ $(AM_LDFLAGS)
+
+#
# Tests
# We add a separate "memcheck" target which runs valgrind on all tests in
# MEMTESTS. Note that we fail if _any_ leak is detected by valgrind. Thus, you
diff --git a/configure.ac b/configure.ac
index 63cac17..091a6c3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -69,6 +69,39 @@ AC_SUBST(CHECK_LIBS)
AM_CONDITIONAL([BUILD_HAVE_CHECK], [test "x$have_check" = "xyes"])
#
+# Optionally, look for gtk+-3 and friends for our gtktsm example.
+# This is linux-only as it uses epoll and friends. Therefore, it's disabled by
+# default.
+#
+
+AC_MSG_CHECKING([whether to build gtktsm])
+AC_ARG_ENABLE([gtktsm],
+ [AS_HELP_STRING([--enable-gtktsm],
+ [whether to build gtktsm])])
+if test "x$enable_gtktsm" = "x" ; then
+ enable_gtktsm="no (default)"
+fi
+AC_MSG_RESULT([$enable_gtktsm])
+
+PKG_CHECK_MODULES([GTKTSM], [gtk+-3.0 cairo pango pangocairo xkbcommon],
+ [have_gtktsm=yes], [have_gtktsm=no])
+AC_SUBST(GTKTSM_CFLAGS)
+AC_SUBST(GTKTSM_LIBS)
+
+if test ! "x$have_gtktsm" = "xyes" ; then
+ if test "x$enable_gtktsm" = "xyes" ; then
+ AC_ERROR([GtkTsm dependencies not found])
+ else
+ enable_gtktsm="no"
+ fi
+elif test "x${enable_gtktsm% *}" = "xyes" ; then
+ enable_gtktsm="yes"
+else
+ enable_gtktsm="no"
+fi
+AM_CONDITIONAL([BUILD_ENABLE_GTKTSM], [test "x$enable_gtktsm" = "xyes"])
+
+#
# debug mode
# If --enable-debug is given, we enable several non-standard debug options. We
# enable a lot of debug options by default, so this option is really only for
@@ -159,6 +192,7 @@ AC_MSG_NOTICE([Build configuration:
host-os: $host_os
Miscellaneous Options:
+ gtktsm: $enable_gtktsm
debug: $enable_debug
optimizations: $enable_optimizations
building tests: $have_check
diff --git a/src/gtktsm/gtktsm-app.c b/src/gtktsm/gtktsm-app.c
new file mode 100644
index 0000000..bfb01d5
--- /dev/null
+++ b/src/gtktsm/gtktsm-app.c
@@ -0,0 +1,200 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+#include "gtktsm-app.h"
+#include "gtktsm-win.h"
+#include "shl-macro.h"
+
+struct _GtkTsmApp {
+ GtkApplication parent;
+};
+
+struct _GtkTsmAppClass {
+ GtkApplicationClass parent_class;
+};
+
+G_DEFINE_TYPE(GtkTsmApp, gtktsm_app, GTK_TYPE_APPLICATION);
+
+static gboolean arg_version;
+
+static GOptionEntry app_options[] = {
+ { "version", 0, 0, G_OPTION_ARG_NONE, &arg_version, "Show version information and exit", NULL },
+ { "font", 0, 0, G_OPTION_ARG_STRING, NULL, "Terminal font", "FONT" },
+ { "sb-size", 0, 0, G_OPTION_ARG_INT, NULL, "Scroll-back buffer size in lines", "COUNT" },
+ { "anti-aliasing", 0, 0, G_OPTION_ARG_STRING, NULL, "Anti-aliasing mode for font rendering", "{none,gray,subpixel,default}" },
+ { "subpixel-order", 0, 0, G_OPTION_ARG_STRING, NULL, "Subpixel order for font rendering", "{rgb,bgr,vrgb,vbgr,default}" },
+ { "show-dirty", 0, 0, G_OPTION_ARG_NONE, NULL, "Mark dirty cells during redraw", NULL },
+ { "debug", 0, 0, G_OPTION_ARG_NONE, NULL, "Enable extensive live-debugging", NULL },
+ { NULL }
+};
+
+static gint app_handle_local_options(GApplication *gapp,
+ GVariantDict *opts)
+{
+ if (arg_version) {
+ g_print("GtkTsm\n");
+ return 0;
+ }
+
+ return -1;
+}
+
+static gint app_command_line(GApplication *gapp,
+ GApplicationCommandLine *cmd)
+{
+ GtkTsmApp *app = GTKTSM_APP(gapp);
+ GtkTsmWin *win;
+ GtkTsmTerminal *term;
+ GVariantDict *dict;
+ const gchar *sval;
+ GVariant *val;
+ int r;
+
+ win = gtktsm_win_new(app);
+ term = gtktsm_win_get_terminal(win);
+ dict = g_application_command_line_get_options_dict(cmd);
+
+ val = g_variant_dict_lookup_value(dict,
+ "font",
+ G_VARIANT_TYPE_STRING);
+ if (val)
+ g_object_set(G_OBJECT(term),
+ "font", g_variant_get_string(val, NULL),
+ NULL);
+
+ val = g_variant_dict_lookup_value(dict,
+ "sb-size",
+ G_VARIANT_TYPE_INT32);
+ if (val)
+ g_object_set(G_OBJECT(term),
+ "sb-size", shl_max(g_variant_get_int32(val), 0),
+ NULL);
+
+ val = g_variant_dict_lookup_value(dict,
+ "anti-aliasing",
+ G_VARIANT_TYPE_STRING);
+ if (val) {
+ sval = g_variant_get_string(val, NULL);
+ if (strcmp(sval, "none") &&
+ strcmp(sval, "gray") &&
+ strcmp(sval, "subpixel") &&
+ strcmp(sval, "default")) {
+ g_application_command_line_printerr(cmd,
+ "invalid anti-aliasing argument: %s\n",
+ sval);
+ r = 1;
+ goto error;
+ }
+
+ g_object_set(G_OBJECT(term),
+ "anti-aliasing", sval,
+ NULL);
+ }
+
+ val = g_variant_dict_lookup_value(dict,
+ "subpixel-order",
+ G_VARIANT_TYPE_STRING);
+ if (val) {
+ sval = g_variant_get_string(val, NULL);
+ if (strcmp(sval, "rgb") &&
+ strcmp(sval, "bgr") &&
+ strcmp(sval, "vrgb") &&
+ strcmp(sval, "vbgr") &&
+ strcmp(sval, "default")) {
+ g_application_command_line_printerr(cmd,
+ "invalid subpixel-order argument: %s\n",
+ sval);
+ r = 1;
+ goto error;
+ }
+
+ g_object_set(G_OBJECT(term),
+ "subpixel-order", sval,
+ NULL);
+ }
+
+ val = g_variant_dict_lookup_value(dict,
+ "show-dirty",
+ G_VARIANT_TYPE_BOOLEAN);
+ if (val)
+ g_object_set(G_OBJECT(term),
+ "show-dirty", g_variant_get_boolean(val),
+ NULL);
+
+ val = g_variant_dict_lookup_value(dict,
+ "debug",
+ G_VARIANT_TYPE_BOOLEAN);
+ if (val)
+ g_object_set(G_OBJECT(term),
+ "debug", g_variant_get_boolean(val),
+ NULL);
+
+ gtktsm_win_run(win);
+ gtk_window_present(GTK_WINDOW(win));
+
+ return 0;
+
+error:
+ gtk_widget_destroy(GTK_WIDGET(win));
+ return r;
+}
+
+static void app_activate(GApplication *gapp)
+{
+ GtkTsmApp *app = GTKTSM_APP(gapp);
+ GtkTsmWin *win;
+
+ win = gtktsm_win_new(app);
+ gtktsm_win_run(win);
+ gtk_window_present(GTK_WINDOW(win));
+}
+
+static void gtktsm_app_init(GtkTsmApp *app)
+{
+ g_application_add_main_option_entries(G_APPLICATION(app),
+ app_options);
+}
+
+static void gtktsm_app_class_init(GtkTsmAppClass *klass)
+{
+ GApplicationClass *gapp_klass;
+
+ gapp_klass = G_APPLICATION_CLASS(klass);
+ gapp_klass->handle_local_options = app_handle_local_options;
+ gapp_klass->command_line = app_command_line;
+ gapp_klass->activate = app_activate;
+}
+
+GtkTsmApp *gtktsm_app_new(void)
+{
+ return g_object_new(GTKTSM_APP_TYPE,
+ "application-id", "org.freedesktop.libtsm.gtktsm",
+ "flags", G_APPLICATION_SEND_ENVIRONMENT |
+ G_APPLICATION_HANDLES_COMMAND_LINE,
+ NULL);
+}
diff --git a/src/gtktsm/gtktsm-app.h b/src/gtktsm/gtktsm-app.h
new file mode 100644
index 0000000..6dea32a
--- /dev/null
+++ b/src/gtktsm/gtktsm-app.h
@@ -0,0 +1,43 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#ifndef GTKTSM_APP_H
+#define GTKTSM_APP_H
+
+#include <gtk/gtk.h>
+#include <stdlib.h>
+
+typedef struct _GtkTsmApp GtkTsmApp;
+typedef struct _GtkTsmAppClass GtkTsmAppClass;
+
+#define GTKTSM_APP_TYPE (gtktsm_app_get_type())
+#define GTKTSM_APP(_obj) (G_TYPE_CHECK_INSTANCE_CAST((_obj), \
+ GTKTSM_APP_TYPE, \
+ GtkTsmApp))
+
+GType gtktsm_app_get_type(void);
+GtkTsmApp *gtktsm_app_new(void);
+
+#endif /* GTKTSM_APP_H */
diff --git a/src/gtktsm/gtktsm-terminal.c b/src/gtktsm/gtktsm-terminal.c
new file mode 100644
index 0000000..aa33e7c
--- /dev/null
+++ b/src/gtktsm/gtktsm-terminal.c
@@ -0,0 +1,1996 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#define G_LOG_DOMAIN "GtkTsm"
+
+#include <cairo.h>
+#include <errno.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <libtsm.h>
+#include <math.h>
+#include <pango/pango.h>
+#include <pango/pangocairo.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <xkbcommon/xkbcommon.h>
+#include "gtktsm-terminal.h"
+#include "shl-htable.h"
+#include "shl-llog.h"
+#include "shl-macro.h"
+#include "shl-pty.h"
+
+/*
+ * Glyph Renderer
+ * With terminal-emulators, we have the problem that our grid is fixed.
+ * Existing text-renderers want to apply kerning and other heuristics to
+ * render fonts properly. We cannot do that. Therefore, we render each glyph
+ * separately and provide them as single-glyph objects to the upper layers.
+ *
+ * We also do some heuristics to calculate the real font-metrics. Most fonts
+ * are not mono-space, so they don't provide any generic metrics. We therefore
+ * render the ASCII glyphs and some more one-column glyphs to get a proper
+ * global metric for the font.
+ */
+
+struct gtktsm_font {
+ unsigned long ref;
+ PangoFontMap *map;
+};
+
+struct gtktsm_face {
+ unsigned long ref;
+ struct gtktsm_font *font;
+ PangoContext *ctx;
+ cairo_antialias_t aa;
+ cairo_subpixel_order_t subpixel;
+
+ struct shl_htable glyphs;
+ unsigned int width;
+ unsigned int height;
+ unsigned int baseline;
+ unsigned int line_thickness;
+ unsigned int underline_pos;
+ unsigned int strikethrough_pos;
+};
+
+enum gtktsm_glyph_format {
+ GTKTSM_GLYPH_INVALID,
+ GTKTSM_GLYPH_A1,
+ GTKTSM_GLYPH_A8,
+ GTKTSM_GLYPH_XRGB32,
+};
+
+struct gtktsm_glyph {
+ unsigned long id;
+
+ unsigned int cwidth;
+ unsigned int format;
+ unsigned int width;
+ int stride;
+ unsigned int height;
+ uint8_t *buffer;
+ cairo_surface_t *surface;
+};
+
+#define gtktsm_glyph_from_id(_id) \
+ shl_htable_offsetof((_id), struct gtktsm_glyph, id)
+
+static void gtktsm_face_free(struct gtktsm_face *face);
+static void gtktsm_glyph_free(struct gtktsm_glyph *glyph);
+
+static int gtktsm_font_new(struct gtktsm_font **out)
+{
+ _shl_free_ struct gtktsm_font *font = NULL;
+
+ if (!out)
+ return -EINVAL;
+
+ font = calloc(1, sizeof(*font));
+ if (!font)
+ return -ENOMEM;
+
+ font->ref = 1;
+
+ font->map = pango_cairo_font_map_get_default();
+ if (font->map) {
+ g_object_ref(font->map);
+ } else {
+ font->map = pango_cairo_font_map_new();
+ if (!font->map)
+ return -ENOMEM;
+ }
+
+ *out = font;
+ font = NULL;
+ return 0;
+}
+
+static void gtktsm_font_ref(struct gtktsm_font *font)
+{
+ if (!font || !font->ref)
+ return;
+
+ ++font->ref;
+}
+
+static void gtktsm_font_unref(struct gtktsm_font *font)
+{
+ if (!font || !font->ref || --font->ref)
+ return;
+
+ g_object_unref(font->map);
+ free(font);
+}
+
+static void init_pango_desc(PangoFontDescription *desc,
+ int desc_size,
+ int desc_bold,
+ int desc_italic)
+{
+ PangoFontMask mask;
+ int v;
+
+ if (desc_size >= 0) {
+ v = desc_size * PANGO_SCALE;
+ if (desc_size > 0 && v > 0)
+ pango_font_description_set_absolute_size(desc, v);
+ }
+
+ if (desc_bold >= 0) {
+ v = desc_bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
+ pango_font_description_set_weight(desc, v);
+ }
+
+ if (desc_italic >= 0) {
+ v = desc_italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL;
+ pango_font_description_set_style(desc, v);
+ }
+
+ pango_font_description_set_variant(desc, PANGO_VARIANT_NORMAL);
+ pango_font_description_set_stretch(desc, PANGO_STRETCH_NORMAL);
+ pango_font_description_set_gravity(desc, PANGO_GRAVITY_SOUTH);
+
+ mask = pango_font_description_get_set_fields(desc);
+
+ if (!(mask & PANGO_FONT_MASK_FAMILY))
+ pango_font_description_set_family(desc, "monospace");
+ if (!(mask & PANGO_FONT_MASK_WEIGHT))
+ pango_font_description_set_weight(desc, PANGO_WEIGHT_NORMAL);
+ if (!(mask & PANGO_FONT_MASK_STYLE))
+ pango_font_description_set_style(desc, PANGO_STYLE_NORMAL);
+ if (!(mask & PANGO_FONT_MASK_SIZE))
+ pango_font_description_set_size(desc, 10 * PANGO_SCALE);
+}
+
+static void measure_pango(struct gtktsm_face *face)
+{
+ static const char str[] = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "@!\"$%&/()=?\\}][{°^~+*#'<>|-_.:,;`´";
+ static const size_t str_len = sizeof(str) - 1;
+ PangoLayout *layout;
+ PangoRectangle rec;
+ unsigned int thick, upos, spos;
+
+ /*
+ * There is no way to check whether a font is a monospace font.
+ * Moreover, there is no "monospace extents" field of fonts that we can
+ * use to calculate a suitable cell size. Any bounding-boxes provided
+ * by the fonts are mostly useless for cell-size computations.
+ * Therefore, we simply render a bunch of ASCII characters and compute
+ * the cell-size from these. If you passed a monospace font, it will
+ * work out greatly. If you passed some other font, you will get a
+ * suitable tradeoff (well, don't do that..).
+ */
+
+ layout = pango_layout_new(face->ctx);
+
+ pango_layout_set_height(layout, 0);
+ pango_layout_set_spacing(layout, 0);
+ pango_layout_set_text(layout, str, str_len);
+ pango_layout_get_pixel_extents(layout, NULL, &rec);
+
+ /* We use an example layout to render a bunch of ASCII characters in a
+ * single line. The height and baseline of the resulting extents can be
+ * copied unchanged into the face. For the width we calculate the
+ * average (rounding up). */
+ face->width = (rec.width + (str_len - 1)) / str_len;
+ face->height = rec.height;
+ face->baseline = PANGO_PIXELS_CEIL(pango_layout_get_baseline(layout));
+
+ /* heuristics to calculate underline/strikethrough positions */
+ thick = shl_min((face->height - face->baseline) / 2, face->height / 14);
+ thick = shl_max(thick, 1U);
+ upos = shl_min(face->baseline + thick, face->height - thick);
+ spos = face->baseline - face->height / 4;
+
+ face->line_thickness = thick;
+ face->underline_pos = upos;
+ face->strikethrough_pos = spos;
+
+ g_object_unref(layout);
+}
+
+static int init_pango(struct gtktsm_face *face,
+ const char *desc_str,
+ int desc_size,
+ int desc_bold,
+ int desc_italic,
+ cairo_antialias_t aa,
+ cairo_subpixel_order_t subpixel)
+{
+ PangoFontDescription *desc;
+ cairo_font_options_t *options;
+
+ /* set context options */
+ pango_context_set_base_dir(face->ctx, PANGO_DIRECTION_LTR);
+ pango_context_set_language(face->ctx, pango_language_get_default());
+
+ /* set font description */
+ desc = pango_font_description_from_string(desc_str);
+ init_pango_desc(desc, desc_size, desc_bold, desc_italic);
+ pango_context_set_font_description(face->ctx, desc);
+ pango_font_description_free(desc);
+
+ /* set anti-aliasing */
+ options = cairo_font_options_create();
+ if (cairo_font_options_status(options) != CAIRO_STATUS_SUCCESS)
+ return -ENOMEM;
+
+ cairo_font_options_set_antialias(options, aa);
+ cairo_font_options_set_subpixel_order(options, subpixel);
+ pango_cairo_context_set_font_options(face->ctx, options);
+ cairo_font_options_destroy(options);
+
+ /* measure font */
+ measure_pango(face);
+
+ if (!face->width || !face->height)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int gtktsm_face_new(struct gtktsm_face **out,
+ struct gtktsm_font *font,
+ const char *desc_str,
+ int desc_size,
+ int desc_bold,
+ int desc_italic,
+ cairo_antialias_t aa,
+ cairo_subpixel_order_t subpixel)
+{
+ struct gtktsm_face *face;
+ int r;
+
+ if (!out || !font || !desc_str)
+ return -EINVAL;
+
+ face = calloc(1, sizeof(*face));
+ if (!face)
+ return -ENOMEM;
+
+ face->font = font;
+ gtktsm_font_ref(face->font);
+ shl_htable_init_ulong(&face->glyphs);
+ face->ctx = pango_font_map_create_context(font->map);
+ face->aa = aa;
+ face->subpixel = subpixel;
+
+ r = init_pango(face,
+ desc_str,
+ desc_size,
+ desc_bold,
+ desc_italic,
+ aa,
+ subpixel);
+ if (r < 0)
+ goto error;
+
+ *out = face;
+ return 0;
+
+error:
+ gtktsm_face_free(face);
+ return r;
+}
+
+static void free_glyph(unsigned long *elem, void *ctx)
+{
+ gtktsm_glyph_free(gtktsm_glyph_from_id(elem));
+}
+
+static void gtktsm_face_free(struct gtktsm_face *face)
+{
+ if (!face)
+ return;
+
+ if (face->ctx)
+ g_object_unref(face->ctx);
+ shl_htable_clear_ulong(&face->glyphs, free_glyph, NULL);
+ gtktsm_font_unref(face->font);
+ free(face);
+}
+
+static unsigned int c2f(cairo_format_t format)
+{
+ switch (format) {
+ case CAIRO_FORMAT_A1:
+ return GTKTSM_GLYPH_A1;
+ case CAIRO_FORMAT_A8:
+ return GTKTSM_GLYPH_A8;
+ case CAIRO_FORMAT_RGB24:
+ return GTKTSM_GLYPH_XRGB32;
+ default:
+ return GTKTSM_GLYPH_INVALID;
+ }
+}
+
+static int create_glyph(struct gtktsm_face *face,
+ struct gtktsm_glyph *glyph,
+ const uint32_t *ch,
+ size_t len)
+{
+ PangoLayoutLine *line;
+ cairo_surface_t *surface;
+ PangoRectangle rec;
+ cairo_format_t format;
+ PangoLayout *layout;
+ cairo_t *cr;
+ size_t cnt;
+ glong ulen;
+ char *val;
+ int r;
+
+ switch (face->aa) {
+ case CAIRO_ANTIALIAS_NONE:
+ format = CAIRO_FORMAT_A1;
+ break;
+ case CAIRO_ANTIALIAS_GRAY:
+ format = CAIRO_FORMAT_A8;
+ break;
+ case CAIRO_ANTIALIAS_SUBPIXEL:
+ /* fallthrough */
+ default:
+ format = CAIRO_FORMAT_RGB24;
+ break;
+ }
+
+ glyph->format = c2f(format);
+ glyph->width = face->width * glyph->cwidth;
+ glyph->stride = cairo_format_stride_for_width(format, glyph->width);
+ glyph->height = face->height;
+
+ glyph->buffer = calloc(1, glyph->stride * glyph->height);
+ if (!glyph->buffer)
+ return -ENOMEM;
+
+ surface = cairo_image_surface_create_for_data(glyph->buffer,
+ format,
+ glyph->width,
+ glyph->height,
+ glyph->stride);
+ if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+ r = -ENOMEM;
+ goto err_buffer;
+ }
+
+ cr = cairo_create(surface);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
+ r = -ENOMEM;
+ goto err_surface;
+ }
+
+ pango_cairo_update_context(cr, face->ctx);
+ layout = pango_layout_new(face->ctx);
+
+ val = g_ucs4_to_utf8(ch, len, NULL, &ulen, NULL);
+ if (!val) {
+ r = -ERANGE;
+ goto err_layout;
+ }
+
+ /* render one line only */
+ pango_layout_set_height(layout, 0);
+ /* no line spacing */
+ pango_layout_set_spacing(layout, 0);
+ /* set text to char [+combining-chars] */
+ pango_layout_set_text(layout, val, ulen);
+
+ g_free(val);
+
+ cnt = pango_layout_get_line_count(layout);
+ if (cnt == 0) {
+ r = -ERANGE;
+ goto err_layout;
+ }
+
+ line = pango_layout_get_line_readonly(layout, 0);
+ pango_layout_line_get_pixel_extents(line, NULL, &rec);
+
+ cairo_move_to(cr, -rec.x, face->baseline),
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ pango_cairo_show_layout_line(cr, line);
+
+ g_object_unref(layout);
+ cairo_destroy(cr);
+ glyph->surface = surface;
+ return 0;
+
+err_layout:
+ g_object_unref(layout);
+ cairo_destroy(cr);
+err_surface:
+ cairo_surface_destroy(surface);
+err_buffer:
+ free(glyph->buffer);
+ glyph->buffer = NULL;
+ return r;
+}
+
+static int gtktsm_face_render(struct gtktsm_face *face,
+ struct gtktsm_glyph **out,
+ unsigned long id,
+ const uint32_t *ch,
+ size_t len,
+ size_t cwidth)
+{
+ struct gtktsm_glyph *glyph;
+ unsigned long *gid;
+ bool b;
+ int r;
+
+ if (!face || !out)
+ return -EINVAL;
+
+ b = shl_htable_lookup_ulong(&face->glyphs, id, &gid);
+ if (b) {
+ *out = gtktsm_glyph_from_id(gid);
+ return 0;
+ }
+
+ if (!len || !ch || !cwidth)
+ return -EINVAL;
+
+ glyph = calloc(1, sizeof(*glyph));
+ if (!glyph)
+ return -ENOMEM;
+
+ glyph->id = id;
+ glyph->cwidth = cwidth;
+
+ r = create_glyph(face, glyph, ch, len);
+ if (r < 0)
+ goto error;
+
+ r = shl_htable_insert_ulong(&face->glyphs, &glyph->id);
+ if (r < 0)
+ goto error;
+
+ *out = glyph;
+ return 0;
+
+error:
+ gtktsm_glyph_free(glyph);
+ return r;
+}
+
+static void gtktsm_glyph_free(struct gtktsm_glyph *glyph)
+{
+ if (glyph->surface)
+ cairo_surface_destroy(glyph->surface);
+ free(glyph->buffer);
+ free(glyph);
+}
+
+/*
+ * Cell Renderer
+ * Gtk uses cairo for rendering. Unfortunately, cairo isn't very suitable for
+ * terminal/cell rendering. Rendering each glyph separately causes like 10
+ * function calls per cell. Therefore, we render our terminal into a shadow
+ * buffer and only tell cairo to blit it onto the widget-buffer.
+ */
+
+struct gtktsm_renderer {
+ unsigned int width;
+ unsigned int height;
+ int stride;
+ uint8_t *data;
+ cairo_surface_t *surface;
+ tsm_age_t age;
+};
+
+struct gtktsm_renderer_ctx {
+ struct gtktsm_renderer *rend;
+ cairo_t *cr;
+
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+ struct gtktsm_face *face_regular;
+ struct gtktsm_face *face_bold;
+ struct gtktsm_face *face_italic;
+ struct gtktsm_face *face_bold_italic;
+ unsigned int cell_width;
+ unsigned int cell_height;
+
+ bool debug;
+};
+
+static int renderer_realloc(struct gtktsm_renderer *rend,
+ unsigned int width,
+ unsigned int height)
+{
+ int stride;
+ uint8_t *data;
+ cairo_surface_t *surface;
+
+ if (!width)
+ width = 1;
+ if (!height)
+ height = 1;
+
+ stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
+ data = malloc(abs(stride * height));
+ if (!data)
+ return -ENOMEM;
+
+ surface = cairo_image_surface_create_for_data(data,
+ CAIRO_FORMAT_ARGB32,
+ width,
+ height,
+ stride);
+ if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+ cairo_surface_destroy(surface);
+ free(data);
+ return -ENOMEM;
+ }
+
+ if (rend->data) {
+ cairo_surface_destroy(rend->surface);
+ free(rend->data);
+ }
+
+ rend->width = width;
+ rend->height = height;
+ rend->stride = stride;
+ rend->data = data;
+ rend->surface = surface;
+ rend->age = 0;
+
+ return 0;
+}
+
+static int gtktsm_renderer_new(struct gtktsm_renderer **out,
+ unsigned int width,
+ unsigned int height)
+{
+ struct gtktsm_renderer *rend;
+ int r;
+
+ if (!out)
+ return -EINVAL;
+
+ rend = calloc(1, sizeof(*rend));
+ if (!rend)
+ return -ENOMEM;
+
+ r = renderer_realloc(rend, width, height);
+ if (r < 0)
+ goto err_rend;
+
+ *out = rend;
+ return 0;
+
+err_rend:
+ free(rend);
+ return r;
+}
+
+static void gtktsm_renderer_free(struct gtktsm_renderer *rend)
+{
+ if (!rend)
+ return;
+
+ cairo_surface_destroy(rend->surface);
+ free(rend->data);
+ free(rend);
+}
+
+static int gtktsm_renderer_resize(struct gtktsm_renderer *rend,
+ unsigned int width,
+ unsigned int height)
+{
+ if (!rend)
+ return -EINVAL;
+
+ return renderer_realloc(rend, width, height);
+}
+
+static void renderer_fill(struct gtktsm_renderer *rend,
+ unsigned int x,
+ unsigned int y,
+ unsigned int width,
+ unsigned int height,
+ uint8_t br, uint8_t bg, uint8_t bb)
+{
+ unsigned int i, tmp;
+ uint8_t *dst;
+ uint32_t out;
+
+ /* clip width */
+ tmp = x + width;
+ if (tmp <= x || x >= rend->width)
+ return;
+ if (tmp > rend->width)
+ width = rend->width - x;
+
+ /* clip height */
+ tmp = y + height;
+ if (tmp <= y || y >= rend->height)
+ return;
+ if (tmp > rend->height)
+ height = rend->height - y;
+
+ /* prepare */
+ dst = rend->data;
+ dst = &dst[y * rend->stride + x * 4];
+ out = (0xff << 24) | (br << 16) | (bg << 8) | bb;
+
+ /* fill buffer */
+ while (height--) {
+ for (i = 0; i < width; ++i)
+ ((uint32_t*)dst)[i] = out;
+
+ dst += rend->stride;
+ }
+}
+
+/* used for debugging; draws a border on the given rectangle */
+static void renderer_highlight(struct gtktsm_renderer *rend,
+ unsigned int x,
+ unsigned int y,
+ unsigned int width,
+ unsigned int height)
+{
+ unsigned int i, j, tmp;
+ uint8_t *dst;
+ uint32_t out;
+
+ /* clip width */
+ tmp = x + width;
+ if (tmp <= x || x >= rend->width)
+ return;
+ if (tmp > rend->width)
+ width = rend->width - x;
+
+ /* clip height */
+ tmp = y + height;
+ if (tmp <= y || y >= rend->height)
+ return;
+ if (tmp > rend->height)
+ height = rend->height - y;
+
+ /* prepare */
+ dst = rend->data;
+ dst = &dst[y * rend->stride + x * 4];
+ out = (0xff << 24) | (0xd0 << 16) | (0x10 << 8) | 0x10;
+
+ /* draw outline into buffer */
+ for (i = 0; i < height; ++i) {
+ ((uint32_t*)dst)[0] = out;
+ ((uint32_t*)dst)[width - 1] = out;
+
+ if (!i || i + 1 == height) {
+ for (j = 0; j < width; ++j)
+ ((uint32_t*)dst)[j] = out;
+ }
+
+ dst += rend->stride;
+ }
+}
+
+static void renderer_blend_a1(uint8_t *dst,
+ unsigned int dst_stride,
+ const uint8_t *src,
+ unsigned int src_stride,
+ unsigned int width,
+ unsigned int height,
+ uint8_t fr, uint8_t fg, uint8_t fb,
+ uint8_t br, uint8_t bg, uint8_t bb)
+{
+ unsigned int i;
+ uint32_t out;
+ uint_fast32_t r, g, b;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ if (src[i / 8] & (1 << (i % 8))) {
+ r = fr;
+ g = fg;
+ b = fb;
+ } else {
+ r = br;
+ g = bg;
+ b = bb;
+ }
+
+ out = (0xff << 24) | (r << 16) | (g << 8) | b;
+ ((uint32_t*)dst)[i] = out;
+ }
+
+ dst += dst_stride;
+ src += src_stride;
+ }
+}
+
+static void renderer_blend_a8(uint8_t *dst,
+ unsigned int dst_stride,
+ const uint8_t *src,
+ unsigned int src_stride,
+ unsigned int width,
+ unsigned int height,
+ uint8_t fr, uint8_t fg, uint8_t fb,
+ uint8_t br, uint8_t bg, uint8_t bb)
+{
+ unsigned int i;
+ uint32_t out;
+ uint_fast32_t r, g, b;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ if (src[i] == 0) {
+ r = br;
+ g = bg;
+ b = bb;
+ } else if (src[i] == 255) {
+ r = fr;
+ g = fg;
+ b = fb;
+ } else {
+ /* Division by 255 (t /= 255) is done with:
+ * t += 0x80
+ * t = (t + (t >> 8)) >> 8
+ * This speeds up the computation by ~20% as
+ * the division is skipped. */
+ r = fr * src[i] + br * (255 - src[i]);
+ r += 0x80;
+ r = (r + (r >> 8)) >> 8;
+
+ g = fg * src[i] + bg * (255 - src[i]);
+ g += 0x80;
+ g = (g + (g >> 8)) >> 8;
+
+ b = fb * src[i] + bb * (255 - src[i]);
+ b += 0x80;
+ b = (b + (b >> 8)) >> 8;
+ }
+
+ out = (0xff << 24) | (r << 16) | (g << 8) | b;
+ ((uint32_t*)dst)[i] = out;
+ }
+
+ dst += dst_stride;
+ src += src_stride;
+ }
+}
+
+static void renderer_blend_xrgb32(uint8_t *dst,
+ unsigned int dst_stride,
+ const uint8_t *src,
+ unsigned int src_stride,
+ unsigned int width,
+ unsigned int height,
+ uint8_t fr, uint8_t fg, uint8_t fb,
+ uint8_t br, uint8_t bg, uint8_t bb)
+{
+ unsigned int i;
+ uint32_t out, mask;
+ uint_fast32_t r, g, b;
+ uint_fast8_t rm, gm, bm;
+
+ while (height--) {
+ for (i = 0; i < width; ++i) {
+ mask = *(uint32_t*)&src[i * 4];
+ rm = (mask & 0x00ff0000) >> 16;
+ gm = (mask & 0x0000ff00) >> 8;
+ bm = mask & 0x000000ff;
+
+ /* Division by 255 (t /= 255) is done with:
+ * t += 0x80
+ * t = (t + (t >> 8)) >> 8
+ * This speeds up the computation by ~20% as
+ * the division is skipped. */
+
+ if (rm == 0) {
+ r = br;
+ } else if (rm == 255) {
+ r = fr;
+ } else {
+ r = fr * rm + br * (255 - rm);
+ r += 0x80;
+ r = (r + (r >> 8)) >> 8;
+ }
+
+ if (gm == 0) {
+ g = bg;
+ } else if (gm == 255) {
+ g = fg;
+ } else {
+ g = fg * gm + bg * (255 - gm);
+ g += 0x80;
+ g = (g + (g >> 8)) >> 8;
+ }
+
+ if (bm == 0) {
+ b = bb;
+ } else if (bm == 255) {
+ b = fb;
+ } else {
+ b = fb * bm + bb * (255 - bm);
+ b += 0x80;
+ b = (b + (b >> 8)) >> 8;
+ }
+
+ out = (0xff << 24) | (r << 16) | (g << 8) | b;
+ ((uint32_t*)dst)[i] = out;
+ }
+
+ dst += dst_stride;
+ src += src_stride;
+ }
+}
+
+static void renderer_blend(struct gtktsm_renderer *rend,
+ const struct gtktsm_glyph *glyph,
+ unsigned int x,
+ unsigned int y,
+ uint8_t fr, uint8_t fg, uint8_t fb,
+ uint8_t br, uint8_t bg, uint8_t bb)
+{
+ unsigned int tmp, width, height;
+ const uint8_t *src;
+ uint8_t *dst;
+
+ /* clip width */
+ tmp = x + glyph->width;
+ if (tmp <= x || x >= rend->width)
+ return;
+ if (tmp > rend->width)
+ width = rend->width - x;
+ else
+ width = glyph->width;
+
+ /* clip height */
+ tmp = y + glyph->height;
+ if (tmp <= y || y >= rend->height)
+ return;
+ if (tmp > rend->height)
+ height = rend->height - y;
+ else
+ height = glyph->height;
+
+ /* prepare */
+ dst = rend->data;
+ dst = &dst[y * rend->stride + x * 4];
+ src = glyph->buffer;
+
+ switch (glyph->format) {
+ case GTKTSM_GLYPH_A1:
+ renderer_blend_a1(dst,
+ rend->stride,
+ src,
+ glyph->stride,
+ width,
+ height,
+ fr, fg, fb,
+ br, bg, bb);
+ break;
+ case GTKTSM_GLYPH_A8:
+ renderer_blend_a8(dst,
+ rend->stride,
+ src,
+ glyph->stride,
+ width,
+ height,
+ fr, fg, fb,
+ br, bg, bb);
+ break;
+ case GTKTSM_GLYPH_XRGB32:
+ renderer_blend_xrgb32(dst,
+ rend->stride,
+ src,
+ glyph->stride,
+ width,
+ height,
+ fr, fg, fb,
+ br, bg, bb);
+ break;
+ default:
+ g_error("invalid glyph format: %d", glyph->format);
+ break;
+ }
+}
+
+static int renderer_draw_cell(struct tsm_screen *screen,
+ uint32_t id,
+ const uint32_t *ch,
+ size_t len,
+ unsigned int cwidth,
+ unsigned int posx,
+ unsigned int posy,
+ const struct tsm_screen_attr *attr,
+ tsm_age_t age,
+ void *data)
+{
+ const struct gtktsm_renderer_ctx *ctx = data;
+ struct gtktsm_renderer *rend = ctx->rend;
+ struct gtktsm_face *face;
+ uint8_t fr, fg, fb, br, bg, bb;
+ unsigned int x, y;
+ struct gtktsm_glyph *glyph;
+ bool skip;
+ int r;
+
+ /* Skip if our age and the cell age is non-zero *and* the cell-age is
+ * smaller than our age. */
+ skip = age && rend->age && age <= rend->age;
+
+ if (skip && !ctx->debug)
+ return 0;
+
+ x = posx * ctx->cell_width;
+ y = posy * ctx->cell_height;
+
+ /* invert colors if requested */
+ if (attr->inverse) {
+ fr = attr->br;
+ fg = attr->bg;
+ fb = attr->bb;
+ br = attr->fr;
+ bg = attr->fg;
+ bb = attr->fb;
+ } else {
+ fr = attr->fr;
+ fg = attr->fg;
+ fb = attr->fb;
+ br = attr->br;
+ bg = attr->bg;
+ bb = attr->bb;
+ }
+
+ /* select correct font */
+ if (attr->bold && ctx->face_bold)
+ face = ctx->face_bold;
+ else
+ face = ctx->face_regular;
+
+ /* !len means background-only */
+ if (!len) {
+ renderer_fill(rend,
+ x,
+ y,
+ ctx->cell_width * cwidth,
+ ctx->cell_height,
+ br, bg, bb);
+ } else {
+ r = gtktsm_face_render(face,
+ &glyph,
+ id,
+ ch,
+ len,
+ cwidth);
+ if (r < 0)
+ renderer_fill(rend,
+ x,
+ y,
+ ctx->cell_width * cwidth,
+ ctx->cell_height,
+ br, bg, bb);
+ else
+ renderer_blend(rend,
+ glyph,
+ x,
+ y,
+ fr, fg, fb,
+ br, bg, bb);
+ }
+
+ if (attr->underline)
+ renderer_fill(rend,
+ x,
+ y + face->underline_pos,
+ ctx->cell_width * cwidth,
+ face->line_thickness,
+ fr, fg, fb);
+
+ if (!skip && ctx->debug)
+ renderer_highlight(rend,
+ x,
+ y,
+ ctx->cell_width * cwidth,
+ ctx->cell_height);
+
+ return 0;
+}
+
+static void gtktsm_renderer_draw(const struct gtktsm_renderer_ctx *ctx)
+{
+ struct gtktsm_renderer *rend = ctx->rend;
+ struct tsm_screen_attr attr;
+ unsigned int w, h;
+
+ /* cairo is *way* too slow to render all masks efficiently. Therefore,
+ * we render all glyphs into a shadow buffer on the CPU and then tell
+ * cairo to blit it into the gtk buffer. This way we get two mem-writes
+ * but at least it's fast enough to render a whole screen. */
+
+ cairo_surface_flush(rend->surface);
+ rend->age = tsm_screen_draw(ctx->screen,
+ renderer_draw_cell,
+ (void*)ctx);
+ cairo_surface_mark_dirty(rend->surface);
+
+ cairo_set_source_surface(ctx->cr, rend->surface, 0, 0);
+ cairo_paint(ctx->cr);
+
+ /* draw padding */
+ w = tsm_screen_get_width(ctx->screen);
+ h = tsm_screen_get_height(ctx->screen);
+ tsm_vte_get_def_attr(ctx->vte, &attr);
+ cairo_set_source_rgb(ctx->cr,
+ attr.br / 255.0,
+ attr.bg / 255.0,
+ attr.bb / 255.0);
+ cairo_move_to(ctx->cr, w * ctx->cell_width, 0);
+ cairo_line_to(ctx->cr, w * ctx->cell_width, h * ctx->cell_height);
+ cairo_line_to(ctx->cr, 0, h * ctx->cell_height);
+ cairo_line_to(ctx->cr, 0, rend->height);
+ cairo_line_to(ctx->cr, rend->width, rend->height);
+ cairo_line_to(ctx->cr, rend->width, 0);
+ cairo_close_path(ctx->cr);
+ cairo_fill(ctx->cr);
+}
+
+/*
+ * GtkTsmTerminal Widget
+ * The GtkTsmTerminal widget is very similar to libvte. It uses libtsm and
+ * the other helpers here to implement a Gtk widget that displays a terminal
+ * emulator.
+ *
+ * The creator of the widget has exclusive control over the PTY process, as it
+ * returns after fork() and before doing any exec().
+ */
+
+typedef struct _GtkTsmTerminalPrivate {
+ /* child objects */
+ struct gtktsm_renderer *rend;
+ struct gtktsm_font *font;
+ struct tsm_screen *screen;
+ struct tsm_vte *vte;
+
+ /* pty bridge */
+ int pty_bridge;
+ GIOChannel *bridge_chan;
+ guint bridge_src;
+
+ /* properties */
+ char *prop_font;
+ cairo_antialias_t prop_aa;
+ cairo_subpixel_order_t prop_subpixel;
+ unsigned int prop_sb_size;
+
+ /* font faces */
+ struct gtktsm_face *face_regular;
+ struct gtktsm_face *face_bold;
+ struct gtktsm_face *face_italic;
+ struct gtktsm_face *face_bold_italic;
+
+ /* selection */
+ unsigned int sel;
+ guint32 sel_start;
+ gdouble sel_x;
+ gdouble sel_y;
+
+ /* pty */
+ struct shl_pty *pty;
+ guint child_src;
+ guint idle_src;
+
+ /* cache */
+ GdkKeymap *keymap;
+ unsigned int width;
+ unsigned int height;
+ unsigned int columns;
+ unsigned int rows;
+
+ bool realized : 1;
+ bool show_dirty : 1;
+ bool debug : 1;
+} GtkTsmTerminalPrivate;
+
+enum {
+ TERMINAL_PROP_0,
+ TERMINAL_PROP_FONT,
+ TERMINAL_PROP_SB_SIZE,
+ TERMINAL_PROP_ANTI_ALIASING,
+ TERMINAL_PROP_SUBPIXEL_ORDER,
+ TERMINAL_PROP_SHOW_DIRTY,
+ TERMINAL_PROP_DEBUG,
+ TERMINAL_PROP_CNT,
+};
+
+enum {
+ TERMINAL_SIGNAL_CHANGED,
+ TERMINAL_SIGNAL_STOPPED,
+ TERMINAL_SIGNAL_CNT,
+};
+
+static GParamSpec *terminal_props[TERMINAL_PROP_CNT];
+static guint terminal_signals[TERMINAL_SIGNAL_CNT];
+
+G_DEFINE_TYPE_WITH_PRIVATE(GtkTsmTerminal,
+ gtktsm_terminal,
+ GTK_TYPE_DRAWING_AREA);
+
+static void terminal_recalculate_cells(GtkTsmTerminal *term,
+ unsigned int width,
+ unsigned int height)
+{
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+
+ p->width = width;
+ p->height = height;
+
+ if (p->face_regular && p->face_regular->width)
+ p->columns = (p->width / p->face_regular->width) ? : 1;
+ else
+ p->columns = 1;
+
+ if (p->face_regular && p->face_regular->height)
+ p->rows = (p->height / p->face_regular->height) ? : 1;
+ else
+ p->rows = 1;
+}
+
+static void terminal_set_font(GtkTsmTerminal *term)
+{
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ struct gtktsm_face *regular;
+ int r;
+
+ r = gtktsm_face_new(&regular,
+ p->font,
+ p->prop_font,
+ -1,
+ 0,
+ 0,
+ p->prop_aa,
+ p->prop_subpixel);
+ if (r < 0)
+ g_error("cannot initialize pango font face (desc: %s)",
+ p->prop_font);
+
+ gtktsm_face_free(p->face_regular);
+ gtktsm_face_free(p->face_bold);
+ gtktsm_face_free(p->face_italic);
+ gtktsm_face_free(p->face_bold_italic);
+
+ /* mandatory regular face */
+ p->face_regular = regular;
+
+ /* optional bold face */
+ r = gtktsm_face_new(&p->face_bold,
+ p->font,
+ p->prop_font,
+ -1,
+ 1,
+ 0,
+ p->prop_aa,
+ p->prop_subpixel);
+ if (r < 0)
+ p->face_bold = NULL;
+
+ /* optional italic face */
+ r = gtktsm_face_new(&p->face_italic,
+ p->font,
+ p->prop_font,
+ -1,
+ 0,
+ 1,
+ p->prop_aa,
+ p->prop_subpixel);
+ if (r < 0)
+ p->face_italic = NULL;
+
+ /* optional bold, italic face */
+ r = gtktsm_face_new(&p->face_bold_italic,
+ p->font,
+ p->prop_font,
+ -1,
+ 1,
+ 1,
+ p->prop_aa,
+ p->prop_subpixel);
+ if (r < 0)
+ p->face_bold_italic = NULL;
+
+ terminal_recalculate_cells(term, p->width, p->height);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+}
+
+static gboolean terminal_configure_fn(GtkWidget *widget,
+ GdkEvent *ev,
+ gpointer data)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(widget);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ GdkEventConfigure *cev = (void*)ev;
+ bool state_changed = false;
+ GdkWindow *wnd;
+ GdkEventMask mask;
+ int r;
+
+ terminal_recalculate_cells(term, cev->width, cev->height);
+
+ r = gtktsm_renderer_resize(p->rend, p->width, p->height);
+ if (r < 0)
+ g_error("gtktsm_renderer_resize() failed: %d", r);
+
+ r = tsm_screen_resize(p->screen, p->columns, p->rows);
+ if (r < 0)
+ g_error("tsm_screen_resize() failed: %d", r);
+
+ if (p->pty) {
+ r = shl_pty_resize(p->pty, p->columns, p->rows);
+ if (r < 0)
+ g_error("shl_pty_resize() failed: %d", r);
+ }
+
+ if (!p->realized) {
+ p->realized = true;
+ wnd = gtk_widget_get_window(widget);
+ mask = gdk_window_get_events(wnd);
+ mask |= GDK_KEY_PRESS_MASK;
+ mask |= GDK_BUTTON_MOTION_MASK;
+ mask |= GDK_BUTTON_PRESS_MASK;
+ mask |= GDK_BUTTON_RELEASE_MASK;
+ gdk_window_set_events(wnd, mask);
+ }
+
+ gtk_widget_queue_draw(widget);
+
+ if (state_changed)
+ g_signal_emit(term,
+ terminal_signals[TERMINAL_SIGNAL_CHANGED],
+ 0);
+
+ return TRUE;
+}
+
+static gboolean terminal_draw_fn(GtkWidget *widget,
+ cairo_t *cr,
+ gpointer data)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(widget);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ struct gtktsm_renderer_ctx ctx;
+ struct tsm_screen_attr attr;
+ int64_t start, end;
+
+ if (!p->face_regular) {
+ tsm_vte_get_def_attr(p->vte, &attr);
+ cairo_set_source_rgb(cr,
+ attr.br / 255.0,
+ attr.bg / 255.0,
+ attr.bb / 255.0);
+ cairo_paint(cr);
+ return FALSE;
+ }
+
+ start = g_get_monotonic_time();
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.debug = p->show_dirty;
+ ctx.rend = p->rend;
+ ctx.cr = cr;
+ ctx.face_regular = p->face_regular;
+ ctx.face_bold = p->face_bold;
+ ctx.face_italic = p->face_italic;
+ ctx.face_bold_italic = p->face_bold_italic;
+ ctx.screen = p->screen;
+ ctx.vte = p->vte;
+ ctx.cell_width = p->face_regular->width;
+ ctx.cell_height = p->face_regular->height;
+
+ gtktsm_renderer_draw(&ctx);
+
+ end = g_get_monotonic_time();
+ if (p->debug)
+ g_message("frame rendered in: %lldms", (long long)((end - start) / 1000));
+
+ return FALSE;
+}
+
+#define ALL_MODS (GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_CONTROL_MASK | \
+ GDK_MOD1_MASK | GDK_MOD4_MASK)
+
+static gboolean terminal_key_fn(GtkWidget *widget,
+ GdkEvent *ev,
+ gpointer data)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(widget);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ GdkEventKey *e = (void*)ev;
+ unsigned int mods = 0;
+ gboolean b;
+ GdkModifierType cmod;
+ guint key;
+ uint32_t ucs4;
+
+ if (e->type != GDK_KEY_PRESS)
+ return FALSE;
+
+ if (e->state & GDK_SHIFT_MASK)
+ mods |= TSM_SHIFT_MASK;
+ if (e->state & GDK_LOCK_MASK)
+ mods |= TSM_LOCK_MASK;
+ if (e->state & GDK_CONTROL_MASK)
+ mods |= TSM_CONTROL_MASK;
+ if (e->state & GDK_MOD1_MASK)
+ mods |= TSM_ALT_MASK;
+ if (e->state & GDK_MOD4_MASK)
+ mods |= TSM_LOGO_MASK;
+
+ if (!p->keymap)
+ p->keymap = gdk_keymap_get_default();
+
+ b = gdk_keymap_translate_keyboard_state(p->keymap,
+ e->hardware_keycode,
+ e->state,
+ e->group,
+ &key,
+ NULL,
+ NULL,
+ &cmod);
+
+ if (b) {
+ if (key == GDK_KEY_Up &&
+ ((e->state & ~cmod & ALL_MODS) == GDK_SHIFT_MASK)) {
+ tsm_screen_sb_up(p->screen, 1);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ return TRUE;
+ } else if (key == GDK_KEY_Down &&
+ ((e->state & ~cmod & ALL_MODS) == GDK_SHIFT_MASK)) {
+ tsm_screen_sb_down(p->screen, 1);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ return TRUE;
+ } else if (key == GDK_KEY_Page_Up &&
+ ((e->state & ~cmod & ALL_MODS) == GDK_SHIFT_MASK)) {
+ tsm_screen_sb_page_up(p->screen, 1);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ return TRUE;
+ } else if (key == GDK_KEY_Page_Down &&
+ ((e->state & ~cmod & ALL_MODS) == GDK_SHIFT_MASK)) {
+ tsm_screen_sb_page_down(p->screen, 1);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ return TRUE;
+ }
+ }
+
+ ucs4 = xkb_keysym_to_utf32(e->keyval);
+ if (!ucs4)
+ ucs4 = TSM_VTE_INVALID;
+
+ if (tsm_vte_handle_keyboard(p->vte, e->keyval, 0, mods, ucs4)) {
+ tsm_screen_sb_reset(p->screen);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean terminal_button_fn(GtkWidget *widget,
+ GdkEvent *ev,
+ gpointer data)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(widget);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ GdkEventButton *e = (void*)ev;
+ unsigned int cell_width, cell_height;
+
+ if (e->button != 1)
+ return FALSE;
+
+ if (!p->face_regular)
+ return FALSE;
+
+ cell_width = p->face_regular->width;
+ cell_height = p->face_regular->height;
+
+ if (e->type == GDK_BUTTON_PRESS) {
+ p->sel = 1;
+ p->sel_start = e->time;
+ p->sel_x = e->x;
+ p->sel_y = e->y;
+ } else if (e->type == GDK_2BUTTON_PRESS) {
+ p->sel = 2;
+ /* TODO: select word */
+ tsm_screen_selection_start(p->screen,
+ e->x / cell_width,
+ e->y / cell_height);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ } else if (e->type == GDK_3BUTTON_PRESS) {
+ p->sel = 2;
+ /* TODO: select line */
+ tsm_screen_selection_start(p->screen,
+ e->x / cell_width,
+ e->y / cell_height);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ } else if (e->type == GDK_BUTTON_RELEASE) {
+ if (p->sel == 1 && p->sel_start + 100 > e->time) {
+ tsm_screen_selection_reset(p->screen);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ } else if (p->sel > 1) {
+ /* TODO: copy */
+ }
+
+ p->sel = 0;
+ }
+
+ return TRUE;
+}
+
+static gboolean terminal_motion_fn(GtkWidget *widget,
+ GdkEvent *ev,
+ gpointer data)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(widget);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ GdkEventMotion *e = (void*)ev;
+ unsigned int cell_width, cell_height;
+
+ if (!p->sel)
+ return TRUE;
+
+ if (!p->face_regular)
+ return FALSE;
+
+ cell_width = p->face_regular->width;
+ cell_height = p->face_regular->height;
+
+ if (p->sel == 1) {
+ if (fabs(p->sel_x - e->x) > 3 ||
+ fabs(p->sel_y - e->y) > 3) {
+ p->sel = 2;
+ tsm_screen_selection_start(p->screen,
+ p->sel_x / cell_width,
+ p->sel_y / cell_height);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ }
+ } else {
+ tsm_screen_selection_target(p->screen,
+ e->x / cell_width,
+ e->y / cell_height);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ }
+
+ return FALSE;
+}
+
+static gboolean terminal_idle_fn(gpointer data)
+{
+ GtkTsmTerminal *term = data;
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ int r;
+
+ p->idle_src = 0;
+ if (!p->pty)
+ return FALSE;
+
+ r = shl_pty_bridge_dispatch_pty(p->pty_bridge, p->pty);
+ if (r < 0)
+ g_error("shl_pty_bridge_dispatch() failed: %d", r);
+
+ return FALSE;
+}
+
+static void terminal_write_fn(struct tsm_vte *vte,
+ const char *u8,
+ size_t len,
+ void *data)
+{
+ GtkTsmTerminal *term = data;
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ int r;
+
+ if (!p->pty)
+ return;
+
+ r = shl_pty_write(p->pty, u8, len);
+ if (r < 0)
+ g_error("OOM in pty-write: %d", r);
+
+ /* dont directly call into pty-bridge to avoid possible recursion */
+ if (!p->idle_src)
+ p->idle_src = g_idle_add(terminal_idle_fn, term);
+}
+
+static void terminal_log_fn(void *data,
+ const char *file,
+ int line,
+ const char *fn,
+ const char *subs,
+ unsigned int sev,
+ const char *format,
+ va_list args)
+{
+ GLogLevelFlags flags = 0;
+
+ switch (sev) {
+ case LLOG_DEBUG:
+ flags |= G_LOG_LEVEL_DEBUG;
+ break;
+ case LLOG_INFO:
+ flags |= G_LOG_LEVEL_INFO;
+ break;
+ case LLOG_NOTICE:
+ flags |= G_LOG_LEVEL_MESSAGE;
+ break;
+ case LLOG_WARNING:
+ flags |= G_LOG_LEVEL_WARNING;
+ break;
+ case LLOG_ERROR:
+ flags |= G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL;
+ break;
+ case LLOG_CRITICAL:
+ case LLOG_ALERT:
+ case LLOG_FATAL:
+ flags |= G_LOG_LEVEL_CRITICAL | G_LOG_FLAG_FATAL;
+ break;
+ }
+
+ g_logv(G_LOG_DOMAIN "-tsm", flags, format, args);
+}
+
+static gboolean terminal_bridge_fn(GIOChannel *chan,
+ GIOCondition cond,
+ gpointer data)
+{
+ GtkTsmTerminal *term = data;
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ int r;
+
+ r = shl_pty_bridge_dispatch(p->pty_bridge, 0);
+ if (r < 0)
+ g_error("shl_pty_bridge_dispatch() failed: %d", r);
+
+ return TRUE;
+}
+
+static void terminal_get_property(GObject *gobj,
+ guint prop,
+ GValue *val,
+ GParamSpec *spec)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(gobj);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ const char *str;
+
+ switch (prop) {
+ case TERMINAL_PROP_FONT:
+ g_value_set_string(val, p->prop_font);
+ break;
+ case TERMINAL_PROP_SB_SIZE:
+ g_value_set_uint(val, p->prop_sb_size);
+ break;
+ case TERMINAL_PROP_ANTI_ALIASING:
+ switch (p->prop_aa) {
+ case CAIRO_ANTIALIAS_NONE:
+ str = "none";
+ break;
+ case CAIRO_ANTIALIAS_GRAY:
+ str = "gray";
+ break;
+ case CAIRO_ANTIALIAS_SUBPIXEL:
+ str = "subpixel";
+ break;
+ default:
+ str = "default";
+ break;
+ }
+
+ g_value_set_string(val, str);
+ break;
+ case TERMINAL_PROP_SUBPIXEL_ORDER:
+ switch (p->prop_subpixel) {
+ case CAIRO_SUBPIXEL_ORDER_RGB:
+ str = "rgb";
+ break;
+ case CAIRO_SUBPIXEL_ORDER_BGR:
+ str = "bgr";
+ break;
+ case CAIRO_SUBPIXEL_ORDER_VRGB:
+ str = "vrgb";
+ break;
+ case CAIRO_SUBPIXEL_ORDER_VBGR:
+ str = "vbgr";
+ break;
+ default:
+ str = "default";
+ break;
+ }
+
+ g_value_set_string(val, str);
+ break;
+ case TERMINAL_PROP_SHOW_DIRTY:
+ g_value_set_boolean(val, p->show_dirty);
+ break;
+ case TERMINAL_PROP_DEBUG:
+ g_value_set_boolean(val, p->debug);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobj, prop, spec);
+ break;
+ }
+}
+
+static void terminal_set_property(GObject *gobj,
+ guint prop,
+ const GValue *val,
+ GParamSpec *spec)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(gobj);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ const char *str;
+ bool update_font = false;
+
+ switch (prop) {
+ case TERMINAL_PROP_FONT:
+ update_font = true;
+ g_free(p->prop_font);
+ p->prop_font = g_strdup(g_value_get_string(val));
+ break;
+ case TERMINAL_PROP_SB_SIZE:
+ p->prop_sb_size = g_value_get_uint(val);
+ tsm_screen_set_max_sb(p->screen, p->prop_sb_size);
+ break;
+ case TERMINAL_PROP_ANTI_ALIASING:
+ update_font = true;
+ str = g_value_get_string(val);
+ if (!strcmp(str, "none"))
+ p->prop_aa = CAIRO_ANTIALIAS_NONE;
+ else if (!strcmp(str, "gray"))
+ p->prop_aa = CAIRO_ANTIALIAS_GRAY;
+ else if (!strcmp(str, "subpixel"))
+ p->prop_aa = CAIRO_ANTIALIAS_SUBPIXEL;
+ else
+ p->prop_aa = CAIRO_ANTIALIAS_DEFAULT;
+ break;
+ case TERMINAL_PROP_SUBPIXEL_ORDER:
+ update_font = true;
+ str = g_value_get_string(val);
+ if (!strcmp(str, "rgb"))
+ p->prop_subpixel = CAIRO_SUBPIXEL_ORDER_RGB;
+ else if (!strcmp(str, "bgr"))
+ p->prop_subpixel = CAIRO_SUBPIXEL_ORDER_BGR;
+ else if (!strcmp(str, "vrgb"))
+ p->prop_subpixel = CAIRO_SUBPIXEL_ORDER_VRGB;
+ else if (!strcmp(str, "vbgr"))
+ p->prop_subpixel = CAIRO_SUBPIXEL_ORDER_VBGR;
+ else
+ p->prop_subpixel = CAIRO_SUBPIXEL_ORDER_DEFAULT;
+ break;
+ case TERMINAL_PROP_SHOW_DIRTY:
+ p->show_dirty = g_value_get_boolean(val);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ break;
+ case TERMINAL_PROP_DEBUG:
+ p->debug = g_value_get_boolean(val);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(gobj, prop, spec);
+ break;
+ }
+
+ /* Only update font if we already have one. Otherwise, a new font is
+ * created on demand. */
+ if (update_font && p->face_regular)
+ terminal_set_font(term);
+}
+
+static void terminal_finalize(GObject *gobj)
+{
+ GtkTsmTerminal *term = GTKTSM_TERMINAL(gobj);
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+
+ if (p->pty) {
+ if (p->child_src)
+ g_source_remove(p->child_src);
+ shl_pty_bridge_remove(p->pty_bridge, p->pty);
+ shl_pty_close(p->pty);
+ shl_pty_unref(p->pty);
+ }
+
+ if (p->idle_src)
+ g_source_remove(p->idle_src);
+
+ gtktsm_face_free(p->face_regular);
+ gtktsm_face_free(p->face_bold);
+ gtktsm_face_free(p->face_italic);
+ gtktsm_face_free(p->face_bold_italic);
+
+ g_free(p->prop_font);
+
+ g_source_remove(p->bridge_src);
+ g_io_channel_unref(p->bridge_chan);
+ shl_pty_bridge_free(p->pty_bridge);
+
+ tsm_vte_unref(p->vte);
+ tsm_screen_unref(p->screen);
+ gtktsm_font_unref(p->font);
+ gtktsm_renderer_free(p->rend);
+
+ G_OBJECT_CLASS(gtktsm_terminal_parent_class)->finalize(gobj);
+}
+
+static void gtktsm_terminal_init(GtkTsmTerminal *term)
+{
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+ int r;
+
+ r = gtktsm_renderer_new(&p->rend, 0, 0);
+ if (r < 0)
+ g_error("gtktsm_renderer_new() failed: %d", r);
+
+ r = gtktsm_font_new(&p->font);
+ if (r < 0)
+ g_error("gtktsm_font_new() failed: %d", r);
+
+ r = tsm_screen_new(&p->screen,
+ terminal_log_fn,
+ term);
+ if (r < 0)
+ g_error("tsm_screen_new() failed: %d", r);
+
+ r = tsm_vte_new(&p->vte,
+ p->screen,
+ terminal_write_fn,
+ term,
+ terminal_log_fn,
+ term);
+ if (r < 0)
+ g_error("tsm_vte_new() failed: %d", r);
+
+ p->pty_bridge = shl_pty_bridge_new();
+ if (p->pty_bridge < 0)
+ g_error("shl_pty_bridge_new() failed: %d",
+ p->pty_bridge);
+
+ p->bridge_chan = g_io_channel_unix_new(p->pty_bridge);
+ p->bridge_src = g_io_add_watch(p->bridge_chan,
+ G_IO_IN,
+ terminal_bridge_fn,
+ term);
+
+ g_signal_connect(G_OBJECT(term),
+ "configure-event",
+ G_CALLBACK(terminal_configure_fn),
+ NULL);
+ g_signal_connect(G_OBJECT(term),
+ "draw",
+ G_CALLBACK(terminal_draw_fn),
+ NULL);
+
+ gtk_widget_set_can_focus(GTK_WIDGET(term), TRUE);
+
+ g_signal_connect(GTK_WIDGET(term),
+ "key-press-event",
+ G_CALLBACK(terminal_key_fn),
+ term);
+ g_signal_connect(GTK_WIDGET(term),
+ "button-press-event",
+ G_CALLBACK(terminal_button_fn),
+ term);
+ g_signal_connect(GTK_WIDGET(term),
+ "button-release-event",
+ G_CALLBACK(terminal_button_fn),
+ term);
+ g_signal_connect(GTK_WIDGET(term),
+ "motion-notify-event",
+ G_CALLBACK(terminal_motion_fn),
+ term);
+}
+
+static void gtktsm_terminal_class_init(GtkTsmTerminalClass *klass)
+{
+ GParamSpec **prop;
+ guint *sig;
+
+ G_OBJECT_CLASS(klass)->finalize = terminal_finalize;
+ G_OBJECT_CLASS(klass)->set_property = terminal_set_property;
+ G_OBJECT_CLASS(klass)->get_property = terminal_get_property;
+
+ /* properties */
+
+ prop = &terminal_props[TERMINAL_PROP_FONT];
+ *prop = g_param_spec_string("font",
+ "Terminal font",
+ "The font to be used for terminal screens",
+ "Monospace",
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
+
+ prop = &terminal_props[TERMINAL_PROP_SB_SIZE];
+ *prop = g_param_spec_uint("sb-size",
+ "Scrollback-buffer size",
+ "Number of lines that are kept in the scrollback buffer",
+ 0,
+ G_MAXUINT,
+ 2000,
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
+
+ prop = &terminal_props[TERMINAL_PROP_ANTI_ALIASING];
+ *prop = g_param_spec_string("anti-aliasing",
+ "Anti-Aliasing",
+ "The anti-aliasing mode for terminal fonts",
+ "default",
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
+
+ prop = &terminal_props[TERMINAL_PROP_SUBPIXEL_ORDER];
+ *prop = g_param_spec_string("subpixel-order",
+ "Subpixel-Order",
+ "The subpixel-order used for anti-aliasing",
+ "default",
+ G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
+
+ prop = &terminal_props[TERMINAL_PROP_SHOW_DIRTY];
+ *prop = g_param_spec_boolean("show-dirty",
+ "Show dirty cells",
+ "Highlight dirty cells to debug terminal rendering",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ prop = &terminal_props[TERMINAL_PROP_DEBUG];
+ *prop = g_param_spec_boolean("debug",
+ "Debug mode",
+ "Enable extensive live debugging",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties(G_OBJECT_CLASS(klass),
+ TERMINAL_PROP_CNT,
+ terminal_props);
+
+ /* signals */
+
+ sig = &terminal_signals[TERMINAL_SIGNAL_CHANGED];
+ *sig = g_signal_new("terminal-changed",
+ G_OBJECT_CLASS_TYPE(G_OBJECT_CLASS(klass)),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL, /* accu + accu-data */
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+
+ sig = &terminal_signals[TERMINAL_SIGNAL_STOPPED];
+ *sig = g_signal_new("terminal-stopped",
+ G_OBJECT_CLASS_TYPE(klass),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL, /* accu + accu-data */
+ g_cclosure_marshal_generic,
+ G_TYPE_NONE, 0);
+}
+
+GtkTsmTerminal *gtktsm_terminal_new(void)
+{
+ return g_object_new(GTKTSM_TYPE_TERMINAL,
+ NULL);
+}
+
+static void terminal_read_fn(struct shl_pty *pty,
+ void *data,
+ char *u8,
+ size_t len)
+{
+ GtkTsmTerminal *term = data;
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+
+ tsm_vte_input(p->vte, u8, len);
+ gtk_widget_queue_draw(GTK_WIDGET(term));
+}
+
+static void terminal_child_fn(GPid pid,
+ gint status,
+ gpointer data)
+{
+ GtkTsmTerminal *term = data;
+ GtkTsmTerminalPrivate *p = gtktsm_terminal_get_instance_private(term);
+
+ g_spawn_close_pid(pid);
+ p->child_src = 0;
+
+ shl_pty_bridge_remove(p->pty_bridge, p->pty);
+ shl_pty_close(p->pty);
+ shl_pty_unref(p->pty);
+ p->pty = NULL;
+
+ g_signal_emit(term,
+ terminal_signals[TERMINAL_SIGNAL_STOPPED],
+ 0);
+}
+
+pid_t gtktsm_terminal_fork(GtkTsmTerminal *term)
+{
+ GtkTsmTerminalPrivate *p;
+ pid_t pid;
+ int r;
+
+ if (!term)
+ return -EINVAL;
+
+ p = gtktsm_terminal_get_instance_private(term);
+ if (p->pty)
+ return -EALREADY;
+
+ if (!p->face_regular)
+ terminal_set_font(term);
+
+ pid = shl_pty_open(&p->pty,
+ terminal_read_fn,
+ term,
+ p->columns,
+ p->rows);
+ if (pid < 0)
+ g_error("shl_pty_open() failed: %d", (int)pid);
+ else if (!pid)
+ return pid;
+
+ r = shl_pty_bridge_add(p->pty_bridge, p->pty);
+ if (r < 0)
+ g_error("shl_pty_bridge_add() failed: %d", r);
+
+ p->child_src = g_child_watch_add(pid, terminal_child_fn, term);
+
+ return pid;
+}
+
+void gtktsm_terminal_kill(GtkTsmTerminal *term, int sig)
+{
+ GtkTsmTerminalPrivate *p;
+
+ if (!term || sig < 1)
+ return;
+
+ p = gtktsm_terminal_get_instance_private(term);
+ if (!p->pty)
+ return;
+
+ shl_pty_signal(p->pty, sig);
+}
diff --git a/src/gtktsm/gtktsm-terminal.h b/src/gtktsm/gtktsm-terminal.h
new file mode 100644
index 0000000..f10fd70
--- /dev/null
+++ b/src/gtktsm/gtktsm-terminal.h
@@ -0,0 +1,77 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#ifndef GTKTSM_TERMINAL_H
+#define GTKTSM_TERMINAL_H
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GtkTsmTerminal GtkTsmTerminal;
+typedef struct _GtkTsmTerminalClass GtkTsmTerminalClass;
+
+struct _GtkTsmTerminal {
+ GtkDrawingArea parent;
+};
+
+struct _GtkTsmTerminalClass {
+ GtkDrawingAreaClass parent_class;
+};
+
+#define GTKTSM_TYPE_TERMINAL (gtktsm_terminal_get_type())
+#define GTKTSM_TERMINAL(_obj) (G_TYPE_CHECK_INSTANCE_CAST((_obj), \
+ GTKTSM_TYPE_TERMINAL, \
+ GtkTsmTerminal))
+#define GTKTSM_TERMINAL_CLASS(_klass) \
+ (G_TYPE_CHECK_CLASS_CAST((_klass), \
+ GTKTSM_TYPE_TERMINAL, \
+ GtkTsmTerminalClass))
+#define GTKTSM_IS_TERMINAL(_obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((_obj), \
+ GTKTSM_TYPE_TERMINAL))
+#define GTKTSM_IS_TERMINAL_CLASS(_klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((_klass), \
+ GTKTSM_TYPE_TERMINAL))
+#define GTKTSM_TERMINAL_GET_CLASS(_obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((_obj), \
+ GTKTSM_TYPE_TERMINAL, \
+ GtkTsmTerminalClass))
+
+#define GTKTSM_TERMINAL_DONT_CARE (-1)
+
+GType gtktsm_terminal_get_type(void) G_GNUC_CONST;
+GtkTsmTerminal *gtktsm_terminal_new(void);
+
+pid_t gtktsm_terminal_fork(GtkTsmTerminal *term);
+void gtktsm_terminal_kill(GtkTsmTerminal *term, int sig);
+
+G_END_DECLS
+
+#endif /* GTKTSM_TERMINAL_H */
diff --git a/src/gtktsm/gtktsm-win.c b/src/gtktsm/gtktsm-win.c
new file mode 100644
index 0000000..c04342f
--- /dev/null
+++ b/src/gtktsm/gtktsm-win.c
@@ -0,0 +1,152 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "gtktsm-app.h"
+#include "gtktsm-terminal.h"
+#include "gtktsm-win.h"
+#include "shl-macro.h"
+
+struct _GtkTsmWin {
+ GtkWindow parent;
+};
+
+struct _GtkTsmWinClass {
+ GtkWindowClass parent_class;
+};
+
+typedef struct _GtkTsmWinPrivate {
+ GtkTsmTerminal *term;
+} GtkTsmWinPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE(GtkTsmWin, gtktsm_win, GTK_TYPE_WINDOW);
+
+static void win_realize(GtkWidget *widget)
+{
+ GtkTsmWin *win = GTKTSM_WIN(widget);
+ GdkWindow *w;
+ GdkRGBA col;
+
+ GTK_WIDGET_CLASS(gtktsm_win_parent_class)->realize(widget);
+
+ w = gtk_widget_get_window(GTK_WIDGET(win));
+ col.red = 0.0;
+ col.green = 0.0;
+ col.blue = 0.0;
+ col.alpha = 1.0;
+ gdk_window_set_background_rgba(w, &col);
+}
+
+static gboolean win_stopped_fn(GtkTsmTerminal *term, gpointer data)
+{
+ GtkTsmWin *win = data;
+
+ gtk_widget_destroy(GTK_WIDGET(win));
+
+ return FALSE;
+}
+
+static void gtktsm_win_init(GtkTsmWin *win)
+{
+ GtkTsmWinPrivate *p = gtktsm_win_get_instance_private(win);
+
+ gtk_window_set_default_size(GTK_WINDOW(win), 800, 600);
+
+ p->term = gtktsm_terminal_new();
+ gtk_container_add(GTK_CONTAINER(win), GTK_WIDGET(p->term));
+ gtk_widget_show(GTK_WIDGET(p->term));
+
+ g_signal_connect(G_OBJECT(p->term),
+ "terminal-stopped",
+ G_CALLBACK(win_stopped_fn),
+ win);
+}
+
+static void gtktsm_win_class_init(GtkTsmWinClass *klass)
+{
+ GTK_WIDGET_CLASS(klass)->realize = win_realize;
+}
+
+GtkTsmWin *gtktsm_win_new(GtkTsmApp *app)
+{
+ return g_object_new(GTKTSM_WIN_TYPE,
+ "application", app,
+ NULL);
+}
+
+void gtktsm_win_run(GtkTsmWin *win)
+{
+ char **argv = (char*[]) {
+ getenv("SHELL") ? : _PATH_BSHELL,
+ NULL
+ };
+ GtkTsmWinPrivate *p;
+ pid_t pid;
+ int r;
+
+ if (!win)
+ return;
+
+ p = gtktsm_win_get_instance_private(win);
+ pid = gtktsm_terminal_fork(p->term);
+ if (pid < 0) {
+ if (pid != -EALREADY)
+ g_error("gtktsm_terminal_fork() failed: %d", (int)pid);
+
+ return;
+ } else if (pid != 0) {
+ /* parent */
+ return;
+ }
+
+ /* child */
+
+ setenv("TERM", "xterm-256color", 1);
+ setenv("COLORTERM", "gtktsm", 1);
+
+ r = execve(argv[0], argv, environ);
+ if (r < 0)
+ g_error("gtktsm_win_run() execve(%s) failed: %m",
+ argv[0]);
+
+ _exit(1);
+}
+
+GtkTsmTerminal *gtktsm_win_get_terminal(GtkTsmWin *win)
+{
+ GtkTsmWinPrivate *p;
+
+ if (!win)
+ return NULL;
+
+ p = gtktsm_win_get_instance_private(win);
+ return p->term;
+}
diff --git a/src/gtktsm/gtktsm-win.h b/src/gtktsm/gtktsm-win.h
new file mode 100644
index 0000000..6d72f71
--- /dev/null
+++ b/src/gtktsm/gtktsm-win.h
@@ -0,0 +1,50 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#ifndef GTKTSM_WIN_H
+#define GTKTSM_WIN_H
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include "gtktsm-app.h"
+#include "gtktsm-terminal.h"
+
+typedef struct _GtkTsmWin GtkTsmWin;
+typedef struct _GtkTsmWinClass GtkTsmWinClass;
+
+#define GTKTSM_WIN_TYPE (gtktsm_win_get_type())
+#define GTKTSM_WIN(_obj) (G_TYPE_CHECK_INSTANCE_CAST((_obj), \
+ GTKTSM_WIN_TYPE, \
+ GtkTsmWin))
+
+GType gtktsm_win_get_type(void);
+GtkTsmWin *gtktsm_win_new(GtkTsmApp *app);
+
+void gtktsm_win_run(GtkTsmWin *win);
+GtkTsmTerminal *gtktsm_win_get_terminal(GtkTsmWin *win);
+
+#endif /* GTKTSM_WIN_H */
diff --git a/src/gtktsm/gtktsm.c b/src/gtktsm/gtktsm.c
new file mode 100644
index 0000000..0dce250
--- /dev/null
+++ b/src/gtktsm/gtktsm.c
@@ -0,0 +1,44 @@
+/*
+ * GtkTsm - Terminal Emulator
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@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.
+ */
+
+#include <gtk/gtk.h>
+#include <locale.h>
+#include <stdlib.h>
+#include "gtktsm-app.h"
+
+int main(int argc, char **argv)
+{
+ GApplication *app;
+ int r;
+
+ setlocale(LC_ALL, "");
+ g_set_application_name("GtkTsm");
+
+ app = G_APPLICATION(gtktsm_app_new());
+ r = g_application_run(app, argc, argv);
+
+ g_object_unref(app);
+ return r;
+}