kmscon: add cairo-renderer module
authorDavid Herrmann <dh.herrmann@googlemail.com>
Tue, 15 Jan 2013 15:30:49 +0000 (16:30 +0100)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Tue, 15 Jan 2013 15:30:49 +0000 (16:30 +0100)
The cairo text renderer uses the cairo blitting functions to blit all
glyphs into the cairo-surface. This is only for testing-purposes. Cairo is
not really suited for blending/blitting of large surfaces. Hence, this
backend is horribly slow compared to bblit/bbulk.

This backend is disabled by default.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Makefile.am
README
configure.ac
src/kmscon_mod_cairo.c [new file with mode: 0644]
src/text.h
src/text_cairo.c [new file with mode: 0644]

index f64d51a..8502e8c 100644 (file)
@@ -450,6 +450,24 @@ mod_gltex_la_LDFLAGS = \
        -module \
        -avoid-version
 
+if BUILD_ENABLE_RENDERER_CAIRO
+module_LTLIBRARIES += mod-cairo.la
+endif
+
+mod_cairo_la_SOURCES = \
+       src/kmscon_module_interface.h \
+       src/githead.h \
+       src/text_cairo.c \
+       src/kmscon_mod_cairo.c
+mod_cairo_la_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       $(CAIRO_CFLAGS)
+mod_cairo_la_LIBADD = \
+       $(CAIRO_LIBS)
+mod_cairo_la_LDFLAGS = \
+       -module \
+       -avoid-version
+
 #
 # Binaries
 # These are the sources for the main binaries and test programs. They mostly
diff --git a/README b/README
index 8def99d..ae872a6 100644 (file)
--- a/README
+++ b/README
@@ -75,6 +75,7 @@ console. See kmscon(1) man-page for usage information.
        - bblit: Simply 2D blitting engine
        - bbulk: Same as bblit but with bulk-requests
        - gltex: OpenGLESv2 accelerated renderer
+       - cairo: cairo based renderer
     --with-sessions: Built in sessions. Available sessions are:
        - dummy: Dummy fallback session
        - terminal: Terminal-emulator sessions
index a24b17b..ea7db2d 100644 (file)
@@ -104,6 +104,11 @@ PKG_CHECK_MODULES([FUSE], [fuse],
 AC_SUBST(FUSE_CFLAGS)
 AC_SUBST(FUSE_LIBS)
 
+PKG_CHECK_MODULES([CAIRO], [cairo],
+                  [have_cairo=yes], [have_cairo=no])
+AC_SUBST(CAIRO_CFLAGS)
+AC_SUBST(CAIRO_LIBS)
+
 #
 # Parse arguments
 # This parses all arguments that are given via "--enable-XY" or "--with-XY" and
@@ -250,9 +255,11 @@ AC_ARG_WITH([renderers],
               [specify list of optional render backends])])
 enable_renderer_bbulk="no"
 enable_renderer_gltex="no"
+enable_renderer_cairo="no"
 if test "x$with_renderers" = "x" ; then
         enable_renderer_bbulk="yes (default)"
         enable_renderer_gltex="yes (default)"
+        enable_renderer_cairo="no (default)"
         with_renderers="bbulk,gltex (default)"
 else
         SAVEIFS="$IFS"
@@ -262,6 +269,8 @@ else
                         enable_renderer_bbulk="yes"
                 elif test "x$i" = "xgltex" ; then
                         enable_renderer_gltex="yes"
+                elif test "x$i" = "xcairo" ; then
+                        enable_renderer_cairo="yes"
                 else
                         IFS="$SAVEIFS"
                         AC_ERROR([Unknown renderer $i])
@@ -541,6 +550,25 @@ else
         renderer_gltex_missing="enable-renderer-gltex"
 fi
 
+# renderer cairo
+renderer_cairo_avail=no
+renderer_cairo_missing=""
+if test ! "x$enable_renderer_cairo" = "xno" ; then
+        renderer_cairo_avail=yes
+        if test "x$have_cairo" = "xno" ; then
+                renderer_cairo_avail=no
+                renderer_cairo_missing="cairo"
+        fi
+
+        if test "x$renderer_cairo_avail" = "xno" ; then
+                if test "x$enable_renderer_cairo" = "xyes" ; then
+                        AC_ERROR([missing for renderer-cairo: $renderer_cairo_missing])
+                fi
+        fi
+else
+        renderer_cairo_missing="enable-renderer-cairo"
+fi
+
 # font unifont
 font_unifont_avail=no
 font_unifont_missing=""
@@ -783,6 +811,14 @@ if test "x$renderer_gltex_avail" = "xyes" ; then
         fi
 fi
 
+# renderer cairo
+renderer_cairo_enabled=no
+if test "x$renderer_cairo_avail" = "xyes" ; then
+        if test "x${enable_renderer_cairo% *}" = "xyes" ; then
+                renderer_cairo_enabled=yes
+        fi
+fi
+
 # renderer bbulk
 renderer_bbulk_enabled=no
 if test "x$renderer_bbulk_avail" = "xyes" ; then
@@ -980,6 +1016,15 @@ fi
 AM_CONDITIONAL([BUILD_ENABLE_RENDERER_GLTEX],
                [test "x$renderer_gltex_enabled" = "xyes"])
 
+# renderer cairo
+if test "x$renderer_cairo_enabled" = "xyes" ; then
+        AC_DEFINE([BUILD_ENABLE_RENDERER_CAIRO], [1],
+                  [Build cairo rendering backend])
+fi
+
+AM_CONDITIONAL([BUILD_ENABLE_RENDERER_CAIRO],
+               [test "x$renderer_cairo_enabled" = "xyes"])
+
 # font unifont
 if test "x$font_unifont_enabled" = "xyes" ; then
         AC_DEFINE([BUILD_ENABLE_FONT_UNIFONT], [1],
@@ -1149,6 +1194,7 @@ AC_MSG_NOTICE([Build configuration:
   Renderers:
                 bbulk: $renderer_bbulk_enabled ($renderer_bbulk_avail: $renderer_bbulk_missing)
                 gltex: $renderer_gltex_enabled ($renderer_gltex_avail: $renderer_gltex_missing)
+                cairo: $renderer_cairo_enabled ($renderer_cairo_avail: $renderer_cairo_missing)
 
   Session Types:
                 dummy: $session_dummy_enabled ($session_dummy_avail: $session_dummy_missing)
diff --git a/src/kmscon_mod_cairo.c b/src/kmscon_mod_cairo.c
new file mode 100644 (file)
index 0000000..3232c51
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * kmscon - Cairo based rendering backend module
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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.
+ */
+
+/*
+ * Cairo based rendering backend
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include "text.h"
+#include "kmscon_module_interface.h"
+#include "log.h"
+
+#define LOG_SUBSYSTEM "mod_cairo"
+
+static int kmscon_cairo_load(void)
+{
+       int ret;
+
+       kmscon_text_cairo_ops.owner = KMSCON_THIS_MODULE;
+       ret = kmscon_text_register(&kmscon_text_cairo_ops);
+       if (ret) {
+               log_error("cannot register cairo renderer");
+               return ret;
+       }
+
+       return 0;
+}
+
+static void kmscon_cairo_unload(void)
+{
+       kmscon_text_unregister(kmscon_text_cairo_ops.name);
+}
+
+KMSCON_MODULE(NULL, kmscon_cairo_load, kmscon_cairo_unload, NULL);
index f2cee79..c61eb94 100644 (file)
@@ -113,5 +113,6 @@ int kmscon_text_render_cb(struct tsm_screen *con, void *data);
 extern struct kmscon_text_ops kmscon_text_bblit_ops;
 extern struct kmscon_text_ops kmscon_text_bbulk_ops;
 extern struct kmscon_text_ops kmscon_text_gltex_ops;
+extern struct kmscon_text_ops kmscon_text_cairo_ops;
 
 #endif /* KMSCON_TEXT_H */
diff --git a/src/text_cairo.c b/src/text_cairo.c
new file mode 100644 (file)
index 0000000..df3b3ee
--- /dev/null
@@ -0,0 +1,466 @@
+/*
+ * kmscon - Cairo Text Renderer Backend
+ *
+ * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@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.
+ */
+
+/*
+ * Cairo based text renderer
+ */
+
+#include <cairo.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include "log.h"
+#include "shl_hashtable.h"
+#include "text.h"
+#include "uterm_video.h"
+
+#define LOG_SUBSYSTEM "text_cairo"
+
+struct tc_glyph {
+       const struct kmscon_glyph *glyph;
+       cairo_surface_t *surf;
+       uint8_t *data;
+};
+
+struct tc_cairo {
+       struct shl_hashtable *glyphs;
+       struct shl_hashtable *bold_glyphs;
+
+       bool new_stride;
+       unsigned int cur;
+       struct uterm_video_buffer buf[2];
+       cairo_surface_t *surf[2];
+       cairo_t *ctx[2];
+
+       bool use_indirect;
+       uint8_t *data[2];
+       struct uterm_video_buffer vbuf;
+};
+
+static int tc_init(struct kmscon_text *txt)
+{
+       struct tc_cairo *tc;
+
+       tc = malloc(sizeof(*tc));
+       if (!tc)
+               return -ENOMEM;
+
+       txt->data = tc;
+       return 0;
+}
+
+static void tc_destroy(struct kmscon_text *txt)
+{
+       struct tc_cairo *tc = txt->data;
+
+       free(tc);
+}
+
+static void free_glyph(void *data)
+{
+       struct tc_glyph *glyph = data;
+
+       cairo_surface_destroy(glyph->surf);
+       free(glyph->data);
+       free(glyph);
+}
+
+static unsigned int format_u2c(unsigned int f)
+{
+       switch (f) {
+       case UTERM_FORMAT_XRGB32:
+               return CAIRO_FORMAT_ARGB32;
+       case UTERM_FORMAT_RGB16:
+               return CAIRO_FORMAT_RGB16_565;
+       case UTERM_FORMAT_GREY:
+               return CAIRO_FORMAT_A8;
+       default:
+               return CAIRO_FORMAT_INVALID;
+       }
+}
+
+static int alloc_indirect(struct kmscon_text *txt,
+                         unsigned int w, unsigned int h)
+{
+       struct tc_cairo *tc = txt->data;
+       unsigned int s, i, format;
+       int ret;
+
+       log_info("using blitting engine");
+
+       format = format_u2c(UTERM_FORMAT_XRGB32);
+       s = cairo_format_stride_for_width(format, w);
+
+       tc->data[0] = malloc(s * h);
+       tc->data[1] = malloc(s * h);
+       if (!tc->data[0] || !tc->data[1]) {
+               log_error("cannot allocate memory for render-buffer");
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       for (i = 0; i < 2; ++i) {
+               tc->surf[i] = cairo_image_surface_create_for_data(tc->data[i],
+                                                                 format,
+                                                                 w, h, s);
+
+               ret = cairo_surface_status(tc->surf[i]);
+               if (ret != CAIRO_STATUS_SUCCESS) {
+                       log_error("cannot create cairo surface: %d", ret);
+                       goto err_cairo;
+               }
+       }
+
+       tc->vbuf.width = w;
+       tc->vbuf.height = h;
+       tc->vbuf.stride = s;
+       tc->vbuf.format = UTERM_FORMAT_XRGB32;
+       tc->use_indirect = true;
+       return 0;
+
+err_cairo:
+       cairo_surface_destroy(tc->surf[1]);
+       cairo_surface_destroy(tc->surf[0]);
+err_free:
+       free(tc->data[1]);
+       free(tc->data[0]);
+       tc->data[1] = NULL;
+       tc->data[0] = NULL;
+       return ret;
+}
+
+static int tc_set(struct kmscon_text *txt)
+{
+       struct tc_cairo *tc = txt->data;
+       int ret, ret2;
+       unsigned int format, w, h;
+       struct uterm_mode *m;
+
+       memset(tc, 0, sizeof(*tc));
+       m = uterm_display_get_current(txt->disp);
+       w = uterm_mode_get_width(m);
+       h = uterm_mode_get_height(m);
+
+       ret = shl_hashtable_new(&tc->glyphs, shl_direct_hash,
+                               shl_direct_equal, NULL,
+                               free_glyph);
+       if (ret)
+               return ret;
+
+       ret = shl_hashtable_new(&tc->bold_glyphs, shl_direct_hash,
+                               shl_direct_equal, NULL,
+                               free_glyph);
+       if (ret)
+               goto err_htable;
+
+       /*
+        * TODO: It is actually faster to use a local shadow buffer and then
+        * blit all data to the framebuffer afterwards. Reads seem to be
+        * horribly slow on some mmap'ed framebuffers. However, that's not true
+        * for all so we actually don't know which to use here.
+        */
+       ret = uterm_display_get_buffers(txt->disp, tc->buf,
+                                       UTERM_FORMAT_XRGB32 |
+                                       UTERM_FORMAT_RGB16);
+       if (ret) {
+               log_warning("cannot get buffers for display %p",
+                           txt->disp);
+               ret = alloc_indirect(txt, w, h);
+               if (ret)
+                       goto err_htable_bold;
+       } else {
+               format = format_u2c(tc->buf[0].format);
+               tc->surf[0] = cairo_image_surface_create_for_data(
+                                                       tc->buf[0].data,
+                                                       format,
+                                                       tc->buf[0].width,
+                                                       tc->buf[0].height,
+                                                       tc->buf[0].stride);
+               format = format_u2c(tc->buf[1].format);
+               tc->surf[1] = cairo_image_surface_create_for_data(
+                                                       tc->buf[1].data,
+                                                       format,
+                                                       tc->buf[1].width,
+                                                       tc->buf[1].height,
+                                                       tc->buf[1].stride);
+
+               ret = cairo_surface_status(tc->surf[0]);
+               ret2 = cairo_surface_status(tc->surf[1]);
+               if (ret != CAIRO_STATUS_SUCCESS ||
+                   ret2 != CAIRO_STATUS_SUCCESS) {
+                       log_error("cannot create cairo surface: %d %d",
+                                 ret, ret2);
+                       cairo_surface_destroy(tc->surf[1]);
+                       cairo_surface_destroy(tc->surf[0]);
+                       ret = alloc_indirect(txt, w, h);
+                       if (ret)
+                               goto err_htable_bold;
+               }
+       }
+
+       tc->ctx[0] = cairo_create(tc->surf[0]);
+       tc->ctx[1] = cairo_create(tc->surf[1]);
+       ret = cairo_status(tc->ctx[0]);
+       ret2 = cairo_status(tc->ctx[1]);
+       if (ret != CAIRO_STATUS_SUCCESS || ret2 != CAIRO_STATUS_SUCCESS) {
+               log_error("cannot create cairo contexts: %d %d", ret, ret2);
+               goto err_ctx;
+       }
+
+       txt->cols = w / txt->font->attr.width;
+       txt->rows = h / txt->font->attr.height;
+
+       return 0;
+
+err_ctx:
+       cairo_destroy(tc->ctx[1]);
+       cairo_destroy(tc->ctx[0]);
+       cairo_surface_destroy(tc->surf[1]);
+       cairo_surface_destroy(tc->surf[0]);
+       free(tc->data[1]);
+       free(tc->data[0]);
+err_htable_bold:
+       shl_hashtable_free(tc->bold_glyphs);
+err_htable:
+       shl_hashtable_free(tc->glyphs);
+       return ret;
+}
+
+static void tc_unset(struct kmscon_text *txt)
+{
+       struct tc_cairo *tc = txt->data;
+
+       cairo_destroy(tc->ctx[1]);
+       cairo_destroy(tc->ctx[0]);
+       cairo_surface_destroy(tc->surf[1]);
+       cairo_surface_destroy(tc->surf[0]);
+       free(tc->data[1]);
+       free(tc->data[0]);
+       shl_hashtable_free(tc->bold_glyphs);
+       shl_hashtable_free(tc->glyphs);
+}
+
+static int find_glyph(struct kmscon_text *txt, struct tc_glyph **out,
+                     uint32_t id, const uint32_t *ch, size_t len, bool bold)
+{
+       struct tc_cairo *tc = txt->data;
+       struct tc_glyph *glyph;
+       struct shl_hashtable *gtable;
+       struct kmscon_font *font;
+       const struct uterm_video_buffer *buf;
+       uint8_t *dst, *src;
+       unsigned int format, i;
+       int ret, stride;
+       bool res;
+
+       if (bold) {
+               gtable = tc->bold_glyphs;
+               font = txt->bold_font;
+       } else {
+               gtable = tc->glyphs;
+               font = txt->font;
+       }
+
+       res = shl_hashtable_find(gtable, (void**)&glyph,
+                                (void*)(unsigned long)id);
+       if (res) {
+               *out = glyph;
+               return 0;
+       }
+
+       glyph = malloc(sizeof(*glyph));
+       if (!glyph)
+               return -ENOMEM;
+       memset(glyph, 0, sizeof(*glyph));
+
+       if (!len)
+               ret = kmscon_font_render_empty(font, &glyph->glyph);
+       else
+               ret = kmscon_font_render(font, id, ch, len, &glyph->glyph);
+
+       if (ret) {
+               ret = kmscon_font_render_inval(font, &glyph->glyph);
+               if (ret)
+                       goto err_free;
+       }
+
+       buf = &glyph->glyph->buf;
+       format = format_u2c(buf->format);
+       glyph->surf = cairo_image_surface_create_for_data(buf->data,
+                                                         format,
+                                                         buf->width,
+                                                         buf->height,
+                                                         buf->stride);
+       ret = cairo_surface_status(glyph->surf);
+       if (ret == CAIRO_STATUS_INVALID_STRIDE) {
+               stride = cairo_format_stride_for_width(format, buf->width);
+               if (!tc->new_stride) {
+                       tc->new_stride = true;
+                       log_debug("wrong stride, copy buffer (%d => %d)",
+                         buf->stride, stride);
+               }
+
+               glyph->data = malloc(stride * buf->height);
+               if (!glyph->data) {
+                       log_error("cannot allocate memory for glyph storage");
+                       ret = -ENOMEM;
+                       goto err_cairo;
+               }
+
+               src = buf->data;
+               dst = glyph->data;
+               for (i = 0; i < buf->height; ++i) {
+                       memcpy(dst, src, buf->width);
+                       dst += stride;
+                       src += buf->stride;
+               }
+
+               cairo_surface_destroy(glyph->surf);
+               glyph->surf = cairo_image_surface_create_for_data(glyph->data,
+                                                                 format,
+                                                                 buf->width,
+                                                                 buf->height,
+                                                                 stride);
+               ret = cairo_surface_status(glyph->surf);
+       }
+       if (ret != CAIRO_STATUS_SUCCESS) {
+               log_error("cannot create cairo-glyph: %d %p %d %d %d %d",
+                         ret, buf->data, format, buf->width, buf->height,
+                         buf->stride);
+               ret = -EFAULT;
+               goto err_cairo;
+       }
+
+       ret = shl_hashtable_insert(gtable, (void*)(long)id, glyph);
+       if (ret)
+               goto err_cairo;
+
+       *out = glyph;
+       return 0;
+
+err_cairo:
+       cairo_surface_destroy(glyph->surf);
+       free(glyph->data);
+err_free:
+       free(glyph);
+       return ret;
+}
+
+static int tc_prepare(struct kmscon_text *txt)
+{
+       struct tc_cairo *tc = txt->data;
+       int ret;
+
+       ret = uterm_display_use(txt->disp, NULL);
+       if (ret < 0) {
+               log_error("cannot use display %p", txt->disp);
+               return ret;
+       }
+
+       tc->cur = ret;
+
+       return 0;
+}
+
+static int tc_draw(struct kmscon_text *txt,
+                  uint32_t id, const uint32_t *ch, size_t len,
+                  unsigned int width,
+                  unsigned int posx, unsigned int posy,
+                  const struct tsm_screen_attr *attr)
+{
+       struct tc_cairo *tc = txt->data;
+       cairo_t *cr = tc->ctx[tc->cur];
+       struct tc_glyph *glyph;
+       int ret;
+
+       if (!width)
+               return 0;
+
+       ret = find_glyph(txt, &glyph, id, ch, len, attr->bold);
+       if (ret)
+               return ret;
+
+       cairo_rectangle(cr,
+                       posx * txt->font->attr.width,
+                       posy * txt->font->attr.height,
+                       txt->font->attr.width,
+                       txt->font->attr.height);
+
+       if (attr->inverse)
+               cairo_set_source_rgb(cr, attr->fr / 255.0, attr->fg / 255.0,
+                                    attr->fb / 255.0);
+       else
+               cairo_set_source_rgb(cr, attr->br / 255.0, attr->bg / 255.0,
+                                    attr->bb / 255.0);
+
+       cairo_fill(cr);
+
+       if (attr->inverse)
+               cairo_set_source_rgb(cr, attr->br / 255.0, attr->bg / 255.0,
+                                    attr->bb / 255.0);
+       else
+               cairo_set_source_rgb(cr, attr->fr / 255.0, attr->fg / 255.0,
+                                    attr->fb / 255.0);
+
+       cairo_mask_surface(cr, glyph->surf,
+                          posx * txt->font->attr.width,
+                          posy * txt->font->attr.height);
+
+       return 0;
+}
+
+static int tc_render(struct kmscon_text *txt)
+{
+       struct tc_cairo *tc = txt->data;
+       int ret;
+
+       cairo_surface_flush(tc->surf[tc->cur]);
+
+       if (!tc->use_indirect)
+               return 0;
+
+       tc->vbuf.data = tc->data[tc->cur];
+       ret = uterm_display_blit(txt->disp, &tc->vbuf, 0, 0);
+       if (ret) {
+               log_error("cannot blit back-buffer to display: %d", ret);
+               return ret;
+       }
+
+       return 0;
+}
+
+struct kmscon_text_ops kmscon_text_cairo_ops = {
+       .name = "cairo",
+       .owner = NULL,
+       .init = tc_init,
+       .destroy = tc_destroy,
+       .set = tc_set,
+       .unset = tc_unset,
+       .prepare = tc_prepare,
+       .draw = tc_draw,
+       .render = tc_render,
+       .abort = NULL,
+};