Console: Add buffer object
authorDavid Herrmann <dh.herrmann@googlemail.com>
Sun, 4 Dec 2011 16:56:43 +0000 (17:56 +0100)
committerDavid Herrmann <dh.herrmann@googlemail.com>
Sun, 4 Dec 2011 16:56:43 +0000 (17:56 +0100)
The buffer object manages the cells and scrollback buffer. It is optimized for
speed: fast rotations, fast resize, etc.

Signed-off-by: David Herrmann <dh.herrmann@googlemail.com>
Makefile.am
src/console.h
src/console_cell.c [new file with mode: 0644]

index 8337175..1d9a7cd 100644 (file)
@@ -18,6 +18,7 @@ libkmscon_core_la_SOURCES = \
        src/console.c src/console.h \
        src/output.c src/output.h \
        src/console_char.c \
+       src/console_cell.c \
        src/log.c src/log.h \
        src/eloop.c src/eloop.h \
        src/vt.c src/vt.h
index 8cbc4a6..680fc3b 100644 (file)
@@ -41,6 +41,7 @@
 
 struct kmscon_char;
 struct kmscon_font;
+struct kmscon_buffer;
 struct kmscon_console;
 
 /* single printable characters */
@@ -65,6 +66,14 @@ void kmscon_font_unref(struct kmscon_font *font);
 int kmscon_font_draw(struct kmscon_font *font, const struct kmscon_char *ch,
                                        cairo_t *cr, uint32_t x, uint32_t y);
 
+/* console buffer with cell objects */
+
+int kmscon_buffer_new(struct kmscon_buffer **out, uint32_t x, uint32_t y);
+void kmscon_buffer_ref(struct kmscon_buffer *buf);
+void kmscon_buffer_unref(struct kmscon_buffer *buf);
+
+int kmscon_buffer_resize(struct kmscon_buffer *buf, uint32_t x, uint32_t y);
+
 /* console objects */
 
 int kmscon_console_new(struct kmscon_console **out);
diff --git a/src/console_cell.c b/src/console_cell.c
new file mode 100644 (file)
index 0000000..f2b0d7b
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * kmscon - Console Buffer and Cell Objects
+ *
+ * Copyright (c) 2011 David Herrmann <dh.herrmann@googlemail.com>
+ * Copyright (c) 2011 University of Tuebingen
+ *
+ * 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.
+ */
+
+/*
+ * Console Buffer and Cell Objects
+ * A console buffer contains all the characters that are printed to the screen
+ * and the whole scrollback buffer. It has a fixed height and width measured in
+ * characters which can be changed on the fly.
+ * The buffer is a linked list of lines. The tail of the list is the current
+ * screen buffer which can be modified by the application. The rest of the list
+ * is the scrollback-buffer.
+ * The linked-list allows fast rotations but prevents fast access. Therefore,
+ * modifications of the scrollback-buffer is prohibited.
+ * For fast access to the current screen buffer, we use an array (cache) of
+ * pointers to the first n lines.
+ * The current screen position can be any line of the scrollback-buffer.
+ *
+ * Y-resize simply adjusts the cache to point to the new lines. X-resize only
+ * modifies the current screen buffer. The scrollback-buffer is not modified to
+ * improve performance.
+ *
+ * Cells
+ * A single cell describes a single character that is printed in that cell. The
+ * character itself is a kmscon_char unicode character. The cell also contains
+ * the color of the character and some other metadata.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cairo.h>
+
+#include "console.h"
+#include "log.h"
+
+#define DEFAULT_WIDTH 80
+#define DEFAULT_HEIGHT 24
+
+struct cell {
+       struct kmscon_char *ch;
+};
+
+struct line {
+       struct line *next;
+       struct line *prev;
+
+       unsigned int num;
+       unsigned int size;
+       struct cell *cells;
+};
+
+struct kmscon_buffer {
+       unsigned long ref;
+
+       unsigned int count;
+       struct line *first;
+       struct line *last;
+
+       unsigned int size_x;
+       unsigned int size_y;
+       unsigned int max_scrollback;
+       struct line **cache;
+       struct line *current;
+};
+
+static void free_line(struct line *line)
+{
+       unsigned int i;
+
+       if (!line)
+               return;
+
+       for (i = 0; i < line->size; ++i) {
+               kmscon_char_free(line->cells[i].ch);
+       }
+
+       free(line->cells);
+       free(line);
+}
+
+/*
+ * Allocates a new line of width \width and pushes it to the tail of the buffer.
+ * The line is filled with blanks. If the maximum number of lines is already
+ * reached, the first line is removed and pushed to the tail.
+ */
+static int push_line(struct kmscon_buffer *buf)
+{
+       struct line *line;
+       struct cell *tmp;
+       unsigned int i, width;
+       int ret;
+
+       if (!buf)
+               return -EINVAL;
+
+       width = buf->size_x;
+       if (!width)
+               width = DEFAULT_WIDTH;
+
+       if (buf->count > (buf->size_y + buf->max_scrollback)) {
+               line = buf->first;
+
+               buf->first = line->next;
+               buf->first->prev = NULL;
+
+               if (buf->current == line)
+                       buf->current = buf->first;
+
+               line->next = NULL;
+               line->prev = NULL;
+               --buf->count;
+       } else {
+               line = malloc(sizeof(*line));
+               if (!line)
+                       return -ENOMEM;
+
+               memset(line, 0, sizeof(*line));
+       }
+
+       if (line->size < width) {
+               tmp = realloc(line->cells, width * sizeof(struct cell));
+               if (!tmp)
+                       goto err_free;
+               memset(&tmp[line->size], 0,
+                               (width - line->size) * sizeof(struct cell));
+               line->cells = tmp;
+               line->size = width;
+       }
+
+       line->num = width;
+       for (i = 0; i < line->num; ++i) {
+               if (line->cells[i].ch) {
+                       ret = kmscon_char_set_u8(line->cells[i].ch, "?", 1);
+               } else {
+                       ret = kmscon_char_new_u8(&line->cells[i].ch, "?", 1);
+               }
+
+               if (ret)
+                       goto err_free;
+       }
+
+       if (buf->last) {
+               line->prev = buf->last;
+               buf->last->next = line;
+               buf->last = line;
+       } else {
+               buf->first = line;
+               buf->last = line;
+       }
+       ++buf->count;
+
+       if (buf->cache) {
+               for (i = 0; i < buf->size_y; ++i)
+                       buf->cache[i] = buf->cache[i]->next;
+       }
+
+       return 0;
+
+err_free:
+       free_line(line);
+       return -ENOMEM;
+}
+
+static int resize_line(struct line *line, unsigned int width)
+{
+       unsigned int i;
+       struct cell *tmp;
+       int ret;
+
+       if (!line)
+               return -EINVAL;
+
+       if (!width)
+               width = DEFAULT_WIDTH;
+
+       if (line->size < width) {
+               tmp = realloc(line->cells, sizeof(struct cell) * width);
+               if (!tmp)
+                       return -ENOMEM;
+
+               memset(&tmp[line->size], 0,
+                               (width - line->size) * sizeof(struct cell));
+               line->cells = tmp;
+               line->size = width;
+       }
+
+       for (i = line->num; i < width; ++i) {
+               if (!line->cells[i].ch) {
+                       ret = kmscon_char_new_u8(&line->cells[i].ch, "?", 1);
+                       if (ret)
+                               return -ENOMEM;
+               }
+       }
+       line->num = width;
+
+       return 0;
+}
+
+int kmscon_buffer_new(struct kmscon_buffer **out, uint32_t x, uint32_t y)
+{
+       struct kmscon_buffer *buf;
+       int ret;
+
+       if (!out)
+               return -EINVAL;
+
+       buf = malloc(sizeof(*buf));
+       if (!buf)
+               return -ENOMEM;
+
+       memset(buf, 0, sizeof(*buf));
+       buf->ref = 1;
+
+       ret = kmscon_buffer_resize(buf, x, y);
+       if (ret)
+               goto err_free;
+
+       *out = buf;
+       return 0;
+
+err_free:
+       free(buf);
+       return ret;
+}
+
+void kmscon_buffer_ref(struct kmscon_buffer *buf)
+{
+       if (!buf)
+               return;
+
+       ++buf->ref;
+}
+
+void kmscon_buffer_unref(struct kmscon_buffer *buf)
+{
+       struct line *iter, *tmp;
+
+       if (!buf || !buf->ref)
+               return;
+
+       if (--buf->ref)
+               return;
+
+       for (iter = buf->first; iter; ) {
+               tmp = iter;
+               iter = iter->next;
+               free_line(tmp);
+       }
+
+       free(buf->cache);
+       free(buf);
+}
+
+int kmscon_buffer_resize(struct kmscon_buffer *buf, uint32_t x, uint32_t y)
+{
+       struct line **cache, *iter;
+       unsigned int old_x, old_y, i;
+       int ret, j;
+
+       if (!buf)
+               return -EINVAL;
+
+       if (!x)
+               x = DEFAULT_WIDTH;
+       if (!y)
+               y = DEFAULT_HEIGHT;
+
+       old_x = buf->size_x;
+       old_y = buf->size_y;
+       buf->size_x = x;
+       buf->size_y = y;
+
+       if (old_y != y) {
+               while (buf->count < y) {
+                       ret = push_line(buf);
+                       if (ret)
+                               goto err_reset;
+               }
+
+               cache = realloc(buf->cache, sizeof(struct line*) * y);
+               if (!cache) {
+                       ret = -ENOMEM;
+                       goto err_reset;
+               }
+
+               memset(cache, 0, sizeof(struct line*) * y);
+               iter = buf->last;
+               for (j = (y - 1); j >= 0; --j) {
+                       cache[j] = iter;
+                       iter = iter->prev;
+               }
+
+               buf->cache = cache;
+       }
+
+       if (old_x != x) {
+               for (i = 0; i < buf->size_y; ++i) {
+                       ret = resize_line(buf->cache[i], x);
+                       if (ret)
+                               goto err_reset;
+               }
+       }
+
+       return 0;
+
+err_reset:
+       buf->size_x = old_x;
+       buf->size_y = old_y;
+       /* TODO: improve error recovery and correctly reset the buffer */
+       return ret;
+}