lt

simple terminal emulator
Log | Files | Refs | git clone https://git.ne02ptzero.me/git/lt

commit 62b8123d3731585f194d4354a0df67dec7bb3a1b
parent bd018a968d78a68f1e46f814ccd516c80e268c16
Author: Ne02ptzero <louis@ne02ptzero.me>
Date:   Wed,  6 Jun 2018 18:52:56 +0200

NEW: First commit of the clean rework

Signed-off-by: Ne02ptzero <louis@ne02ptzero.me>

Diffstat:
MMakefile | 4++--
Aconfig.h | 20++++++++++++++++++++
Afont.c | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Afont.h | 26++++++++++++++++++++++++++
Mmain.c | 28+++++++++++++++-------------
Dopt.c | 72------------------------------------------------------------------------
Dopt.h | 18------------------
Mterm.c | 235++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mterm.h | 108++++++++++++++++++++++++++++++++++++++++----------------------------------------
Autils.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mx.c | 434+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mx.h | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
12 files changed, 870 insertions(+), 369 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,8 +1,8 @@ NAME = lt CC = gcc -SFLAGS = -Wall -Wextra -Werror -g -O2 -LFLAGS = -lXft -lX11 +SFLAGS = -Wall -Wextra -Werror -Wno-unused-variable -g -O2 +LFLAGS = -lXft -lX11 -lfontconfig IFLAGS = -I/usr/X11R6/include -I/usr/include/freetype2 CFLAGS = $(SFLAGS) $(IFLAGS) $(LFLAGS) diff --git a/config.h b/config.h @@ -0,0 +1,20 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define TERM_NAME "lt-256color" +#define DEFAULT_BG 0 +#define MOUSE_FG 7 +#define MOUSE_BG 0 +#define BORDER_PX 2 +#define DEFAULT_BG 0 +#define DEFAULT_FG 7 +/** + * Tabs are 8 characters, and thus indentations are also 8 characters. + * There are heretic movements that try to make indentations 4 (or even 2!) + * characters deep, and that is akin to trying to define the value of PI to + * be 3. + */ +// CF: linux/Documentation/CodingStyle +#define TABSPACES 8 + +#endif /* CONFIG_H */ diff --git a/font.c b/font.c @@ -0,0 +1,161 @@ +#include "font.h" +#include "utils.h" + +#include <math.h> + +static bool font_load(x_window_t *x, FcPattern *pattern, font_t *f) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int want_attr; + int have_attr; + + configured = FcPatternDuplicate(pattern); + if (configured == NULL) + return false; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(x->dsp, x->screen, configured); + + match = FcFontMatch(NULL, configured, &result); + if (match == NULL) + { + FcPatternDestroy(configured); + return false; + } + + f->match = XftFontOpenPattern(x->dsp, match); + if (f->match == NULL) + { + FcPatternDestroy(match); + FcPatternDestroy(configured); + return false; + } + + if (XftPatternGetInteger(pattern, "slant", 0, &want_attr) == XftResultMatch) + { + if (XftPatternGetInteger(f->match->pattern, "slant", 0, &have_attr) + != XftResultMatch || have_attr < want_attr) + { + f->bad_slant = 1; + ERROR("Font slant does not match"); + } + } + + if (XftPatternGetInteger(pattern, "weight", 0, &want_attr) == XftResultMatch) + { + if (XftPatternGetInteger(f->match->pattern, "weight", 0, &have_attr) + != XftResultMatch || have_attr != want_attr) + { + f->bad_weight = 1; + ERROR("Font weight does not match"); + } + } + + XftTextExtentsUtf8(x->dsp, f->match, + (const FcChar8 *)__ascii_printable, sizeof(__ascii_printable), &extents); + f->set = NULL; + f->pattern = configured; + + f->ascent = f->ascent; + f->descent = f->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, sizeof(__ascii_printable)); + + return true; +} + +bool fonts_load(x_window_t *x, const char *font_str, double size) +{ + FcPattern *pattern; + double font_val; + + if (font_str[0] == '-') + pattern = XftXlfdParse(font_str, False, False); + else + pattern = FcNameParse((FcChar8 *)font_str); + + if (pattern == NULL) + { + ERROR("Could not open font %s", font_str); + return false; + } + + if (size > 1) + { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, size); + x->dc.used_font_size = size; + } + else + { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &font_val) == FcResultMatch) + x->dc.used_font_size = font_val; + else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &font_val) == FcResultMatch) + x->dc.used_font_size = -1; + else + { + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + x->dc.used_font_size = 12; + } + } + + if (!font_load(x, pattern, &x->dc.font)) + goto error; + + if (x->dc.used_font_size < 0) + { + FcPatternGetDouble(x->dc.font.match->pattern, FC_PIXEL_SIZE, 0, &font_val); + x->dc.used_font_size = font_val; + } + + x->term.cw = ceilf(x->dc.font.width); + x->term.ch = ceilf(x->dc.font.height); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (!font_load(x, pattern, &x->dc.ifont)) + goto error; + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (!font_load(x, pattern, &x->dc.ibfont)) + goto error; + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (!font_load(x, pattern, &x->dc.bfont)) + goto error; + + FcPatternDestroy(pattern); + + return true; + +error: + ERROR("Can't open font %s", font_str); + + FcPatternDestroy(pattern); + return false; +} + +static void font_unload(x_window_t *x, font_t *f) +{ + XftFontClose(x->dsp, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void fonts_unload(x_window_t *x) +{ + font_unload(x, &x->dc.font); + font_unload(x, &x->dc.bfont); + font_unload(x, &x->dc.ifont); + font_unload(x, &x->dc.ibfont); +} diff --git a/font.h b/font.h @@ -0,0 +1,26 @@ +#ifndef FONT_H +#define FONT_H + +#include <X11/Xft/Xft.h> +#include <stdbool.h> + +typedef struct { + int height; + int width; + int ascent; + int descent; + int bad_slant; + int bad_weight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} font_t; + +#include "x.h" + +bool fonts_load(x_window_t *x, const char *font_str, double size); +void fonts_unload(x_window_t *x); + +#endif /* FONT_H */ diff --git a/main.c b/main.c @@ -1,27 +1,29 @@ #include <stdio.h> #include <locale.h> -#include "opt.h" -#include "term.h" #include "x.h" +#include "term.h" -int main(int ac, const char **av) +void lt_setenv(x_window_t *x) { - opt_t arg = { 0 }; - term_t term = { 0 }; + char buf[sizeof(long) * 8 + 1] = { 0 }; - opt_parse(&arg, ac, av); + snprintf(buf, sizeof(buf), "%lu", x->win); + setenv("WINDOWID", buf, 1); +} +int main(void) +{ setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); + x_window_t x = { 0 }; + term_t t = { 0 }; - term_init(&term, &arg); - term_xinit(&term); - term_pty(&term); - term_resize(&term); - term_spawn(&term); - - term_run(&term); + x_window_init(&x, 80, 25); + term_init(&t, 80, 25); + lt_setenv(&x); + x_window_dtr(&x); + term_dtr(&t); return 0; } diff --git a/opt.c b/opt.c @@ -1,72 +0,0 @@ -#include "opt.h" - -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <errno.h> -#include <stdbool.h> - -static void help(const char *bin_name, int code) -{ - fprintf(stdout, "%s [-c col] [-r row] [-f font] [-t title]\n", bin_name); - exit(code); -} - -static inline bool str_to_int32(const char *in, uint32_t *out) -{ - char *end; - - errno = 0; - *out = strtol(in, &end, 10); - - if (end == in || strlen(end) > 0 || errno) - return false; - - return true; -} - -#define NEED_ARG(ac, av, i) do { \ - if (i + 1 >= ac) \ - { \ - help(av[0], 1); \ - } \ - i++; \ - } while(0) - -void opt_parse(opt_t *ret, int ac, const char **av) -{ - for (int i = 1; i < ac; i++) - { - const char *arg = av[i]; - - if (strlen(arg) > 1 && arg[0] == '-') - { - switch (arg[1]) - { - case 'c': - NEED_ARG(ac, av, i); - if (!str_to_int32(av[i], &ret->cols)) - help(av[0], 1); - break; - case 'r': - NEED_ARG(ac, av, i); - if (!str_to_int32(av[i], &ret->rows)) - help(av[0], 1); - break; - case 't': - NEED_ARG(ac, av, i); - ret->title = av[i]; - break; - case 'f': - NEED_ARG(ac, av, i); - ret->font = av[i]; - break; - case 'h': - help(av[0], 0); - break; - default: - help(av[0], 1); - } - } - } -} diff --git a/opt.h b/opt.h @@ -1,18 +0,0 @@ -#ifndef OPT_H -#define OPT_H - -#include <stdint.h> -#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) - -typedef struct { - uint32_t cols; - uint32_t rows; - const char *title; - const char *font; - const char *bg; - const char *fg; -} opt_t; - -void opt_parse(opt_t *ret, int ac, const char **av); - -#endif /* OPT_H */ diff --git a/term.c b/term.c @@ -1,69 +1,214 @@ -#define _XOPEN_SOURCE 600 -#include <sys/ioctl.h> -#include <termios.h> -#include <assert.h> -#include <unistd.h> -#include <fcntl.h> +#include "term.h" +#include "config.h" +#include "utils.h" + #include <stdlib.h> +#include <string.h> -#include "term.h" +void term_resize(term_t *t, int col, int row) +{ + int i; + int min_row = MIN(row, t->row); + int min_col = MIN(col, t->col); + int *bp; + cursor_t c; + + for (i = 0; i <= t->c.y - row; i++) + { + free(t->line[i]); + free(t->alt[i]); + } + + if (i > 0) + { + memmove(t->line, t->line + i, row * sizeof(line_t)); + memmove(t->alt, t->alt + i, row * sizeof(line_t)); + } + + for (i += row; i < t->row; i++) + { + free(t->line[i]); + free(t->alt[i]); + } + + t->line = realloc(t->line, row * sizeof(line_t)); + t->alt = realloc(t->alt, row * sizeof(line_t)); + t->dirty = realloc(t->dirty, row * sizeof(*t->dirty)); + t->tabs = realloc(t->tabs, col * sizeof(*t->tabs)); + + for (i = 0; i < min_row; i++) + { + t->line[i] = realloc(t->line[i], col * sizeof(line_t)); + t->alt[i] = realloc(t->line[i], col * sizeof(line_t)); + } + + for (; i < row; i++) + { + t->line[i] = malloc(col * sizeof(line_t)); + t->alt[i] = malloc(col * sizeof(line_t)); + } + + /* XXX: ^ Safe those allocations */ + + if (col > t->col) + { + bp = t->tabs + t->col; + + memset(bp, 0, sizeof(*t->tabs) * (col - t->col)); + while (--bp > t->tabs && !*bp) + /* Nothingness ... */; + for (bp += TABSPACES; bp < t->tabs + col; bp += TABSPACES) + *bp = 1; + } + + t->col = col; + t->row = row; + + term_set_scroll(t, 0, row - 1); + term_move_to(t, t->c.x, t->c.y); + + c = t->c; + for (i = 0; i < 2; i++) + { + if (min_col < col && 0 < min_row) + { + term_clear_region(t, min_col, 0, col - 1, min_row - 1); + } + if (col > 0 && min_row < row) + { + term_clear_region(t, 0, min_row, col - 1, row - 1); + } + + term_swap_screen(t); + term_cursor(t, CURSOR_LOAD); + } + t->c = c; +} + +void term_init(term_t *t, int col, int row) +{ + t->c.attr.fg = DEFAULT_FG; + t->c.attr.bg = DEFAULT_BG; -#include <stdio.h> + term_resize(t, col, row); +} -void term_init(term_t *term, opt_t *arg) +void term_dtr(term_t *t) { - term->cols = MAX(arg->cols, 80); - term->rows = MAX(arg->rows, 25); - term->font = arg->font; - term->bg = arg->bg == NULL ? "#000000" : arg->fg; - term->fg = arg->fg == NULL ? "#ffffff" : arg->bg; - term->font = arg->font; - term->title = arg->title == NULL ? "lt" : arg->title; + for (int i = 0; i < t->row; i++) + { + free(t->line[i]); + free(t->alt[i]); + } + + free(t->line); + free(t->alt); + free(t->dirty); + free(t->tabs); } -void term_pty(term_t *term) +void term_set_scroll(term_t *t, int a, int b) { - char *slave_name; + int tmp; + + LIMIT(a, 0, t->row - 1); + LIMIT(b, 0, t->row - 1); - term->pty.master = posix_openpt(O_RDWR | O_NOCTTY); - assert(term->pty.master != -1); + if (a > b) + tmp = a, a = b, b = tmp; - grantpt(term->pty.master); - unlockpt(term->pty.master); + t->top = a; + t->bot = b; +} + +void term_move_to(term_t *t, int x, int y) +{ + int min_y; + int max_y; + + if (t->c.state & CURSOR_ORIGIN) + { + min_y = t->top; + max_y = t->bot; + } + else + { + min_y = 0; + max_y = t->row - 1; + } - slave_name = ptsname(term->pty.master); - term->pty.slave = open(slave_name, O_RDWR | O_NOCTTY); + t->c.state &= ~CURSOR_WRAPNEXT; + t->c.x = LIMIT(x, 0, t->col - 1); + t->c.y = LIMIT(y, min_y, max_y); +} + +void term_clear_region(term_t *t, int x1, int y1, int x2, int y2) +{ + int tmp; + line_t *gp; + + if (x1 > x2) + tmp = x1, x1 = x2, x2 = tmp; + if (y1 > y2) + tmp = y1, y1 = y2, y2 = tmp; + + LIMIT(x1, 0, t->col - 1); + LIMIT(x2, 0, t->col - 1); + LIMIT(y1, 0, t->row - 1); + LIMIT(y2, 0, t->row - 1); + + for (int y = y1; y <= y2; y++) + { + t->dirty[y] = 1; + for (int x = x1; x <= x2; x++) + { + gp = &t->line[y][x]; + /*if (selected(x, y))*/ + /*selclear();*/ + gp->fg = t->c.attr.fg; + gp->bg = t->c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void term_set_dirt(term_t *t, int top, int bot) +{ + LIMIT(top, 0, t->row - 1); + LIMIT(bot, 0, t->row - 1); + + for (int i = top; i <= bot; i++) + t->dirty[i] = 1; +} + +void term_full_dirt(term_t *t) +{ + term_set_dirt(t, 0, t->row - 1); } -void term_resize(term_t *term) +void term_swap_screen(term_t *t) { - struct winsize ws = { - .ws_col = term->cols, - .ws_row = term->rows - }; + line_t **tmp = t->line; - ioctl(term->pty.master, TIOCSWINSZ, &ws); + t->line = t->alt; + t->alt = tmp; + t->mode ^= MODE_ALTSCREEN; + term_full_dirt(t); } -#define SHELL "/bin/sh" -void term_spawn(term_t *term) +void term_cursor(term_t *t, int mode) { - char *env[] = { NULL }; - pid_t p = fork(); + static cursor_t c[2]; + int alt = t->mode & MODE_ALTSCREEN; - if (p == 0) + if (mode == CURSOR_SAVE) { - close(term->pty.master); - setsid(); - ioctl(term->pty.slave, TIOCSCTTY, NULL); - dup2(term->pty.slave, 0); - dup2(term->pty.slave, 1); - dup2(term->pty.slave, 2); - close(term->pty.slave); - execle(SHELL, "-" SHELL, NULL, env); + c[alt] = t->c; } - else if (p > 0) + else if (mode == CURSOR_LOAD) { - close(term->pty.slave); + t->c = c[alt]; + term_move_to(t, c[alt].x, c[alt].y); } } diff --git a/term.h b/term.h @@ -2,68 +2,68 @@ #define TERM_H #include <stdint.h> -#include <X11/Xlib.h> -#include "opt.h" typedef struct { - uint32_t u; - uint16_t mode; - uint32_t fg; - uint32_t bg; -} char_t; + uint_least32_t u; /*!< Character code (UTF-8) */ + unsigned short mode; /*!< Attribute flags */ + uint32_t fg; /*!< Foreground color */ + uint32_t bg; /*!< Background color */ +} line_t; typedef struct { - uint32_t cols; - uint32_t rows; - const char *font; - const char *bg; - const char *fg; - const char *title; + line_t attr; /*!< Current char attributes */ + int x; /*!< X position */ + int y; /*!< Y position */ + uint8_t state; /*!< Cursor state */ +} cursor_t; - unsigned char *buf; +enum cursor_state { + CURSOR_DEFAULT = 0, + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 +}; - struct { - uint32_t x; -#define __x cursor.x - uint32_t y; -#define __y cursor.y - char_t attr; - uint8_t state; - } cursor; - - struct { - int fd; - Display *dsp; -#define __dsp win.dsp - Colormap cmap; -#define __cmap win.cmap - Window win; -#define __win win.win - Window root; - XFontStruct *font; -#define __font win.font - int scr; -#define __scr win.scr - uint32_t font_width; - uint32_t font_height; - - uint32_t bg; - uint32_t fg; - - GC termgc; - } win; - - struct { - int master; - int slave; - } pty; +enum cursor_mouvement { + CURSOR_SAVE, + CURSOR_LOAD +}; +typedef struct { + int row; /*!< Number of rows */ + int col; /*!< Number of columns */ + line_t **line; /*!< Screen buffer */ + line_t **alt; /*!< Alternate Screen buffer */ + int *dirty; /*!< Dirtyness of lines */ + cursor_t c; /*!< Cursor */ + int ocx; /*!< Old cursor column */ + int ocy; /*!< Old cursor row */ + int top; /*!< Top scroll limit */ + int bot; /*!< Bottom scroll limit */ + int mode; /*!< Terminal mode flags */ + int esc; /*!< Escape state flags */ + char trant_bl[4]; /*!< Charset table translation */ + int charset; /*!< Current charset */ + int icharset; /*!< Selected charset for sequence */ + int *tabs; } term_t; -void term_init(term_t *term, opt_t *arg); -void term_pty(term_t *term); -void term_resize(term_t *term); -void term_run(term_t *term); -void term_spawn(term_t *term); +enum term_mode { + MODE_WRAP = 1 << 0, + MODE_INSERT = 1 << 1, + MODE_ALTSCREEN = 1 << 2, + MODE_CRLF = 1 << 3, + MODE_ECHO = 1 << 4, + MODE_PRINT = 1 << 5, + MODE_UTF8 = 1 << 6, + MODE_SIXEL = 1 << 7, +}; + +void term_init(term_t *t, int col, int row); +void term_dtr(term_t *t); +void term_set_scroll(term_t *t, int a, int b); +void term_move_to(term_t *t, int x, int y); +void term_clear_region(term_t *t, int x1, int y1, int x2, int y2); +void term_swap_screen(term_t *t); +void term_cursor(term_t *t, int mode); #endif /* TERM_H */ diff --git a/utils.h b/utils.h @@ -0,0 +1,50 @@ +#ifndef UTILS_H +#define UTILS_H + +#include <stdio.h> + +#define ERROR(str, ...) fprintf(stderr, "Error: " str "\n", ##__VA_ARGS__) + +#define COUNT_OF(ptr) sizeof(ptr) / sizeof(ptr[0]) +#define STATIC_ARRAY_FOREACH(item, array) \ + for (size_t __f_k = 1, __f_i = 0; __f_k && __f_i < COUNT_OF(array); __f_k = !__f_k, __f_i++) \ + for (item = &array[__f_i]; __f_k; __f_k = !__f_k) + +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define MIN(X, Y) ((X) > (Y) ? (Y) : (X)) +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(a, b) (((a) + ((b) - 1)) / (b)) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) + +static inline unsigned short sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +static const char *__color_name[] = { + "black", + "red3", + "green3", + "yellow3", + "blue2", + "magenta3", + "cyan3", + "gray90", + "gray50", + "red", + "green", + "yellow", + "#5c5cff", + "magenta", + "cyan", + "white", + + [255] = 0 +}; + +static const char *__ascii_printable = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + +#endif /* UTILS_H */ diff --git a/x.c b/x.c @@ -11,227 +11,333 @@ #include <ctype.h> #include <time.h> -#include "term.h" +#include "x.h" +#include "utils.h" +#include "config.h" -void term_xinit(term_t *term) +static int x_geo_mask_to_gravity(int mask) { - XColor color; - Atom wm_name; - XSetWindowAttributes wa = { - .background_pixmap = ParentRelative, - .event_mask = KeyPressMask | KeyReleaseMask | ExposureMask, - }; - - (void)wa; - term->__dsp = XOpenDisplay(NULL); - assert(term->__dsp != NULL); + switch (mask & (XNegative | YNegative)) + { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } - term->__scr = XDefaultScreen(term->__dsp); - term->win.root = RootWindow(term->__dsp, term->__scr); - term->win.fd = ConnectionNumber(term->__dsp); + return SouthEastGravity; +} - term->__font = XLoadQueryFont(term->__dsp, term->font == NULL ? "fixed" : NULL); - assert(term->__font != NULL); - term->win.font_width = XTextWidth(term->__font, "m", 1); - term->win.font_height = term->__font->ascent + term->__font->descent; - term->__cmap = DefaultColormap(term->__dsp, term->__scr); +#define XTERM_COLORS 6 * 6 * 6 + 16 +static int x_window_load_color(x_window_t *x, int i, const char *name, XftColor *col) +{ + XRenderColor color = { .alpha = 0xFFFF }; - XAllocNamedColor(term->__dsp, term->__cmap, term->bg, &color, &color); - term->win.bg = color.pixel; - XAllocNamedColor(term->__dsp, term->__cmap, term->fg, &color, &color); - term->win.fg = color.pixel; + if (name == NULL) + { + if (BETWEEN(i, 16, 255)) + { + if (i < XTERM_COLORS ) + { + color.red = sixd_to_16bit(((i - 16) / 36) % 6); + color.green = sixd_to_16bit(((i - 16) / 6) % 6); + color.blue = sixd_to_16bit(((i - 16) / 1) % 6); + } + else + { + color.red = 0x0808 + 0x0a0a * (i - XTERM_COLORS); + color.green = color.blue = color.red; + } - term->buf = calloc(term->cols * term->rows, sizeof(*term->buf)); - assert(term->buf != NULL); + return XftColorAllocValue(x->dsp, x->vis, x->cmap, &color, col); + } + else + name = __color_name[i]; + } - term->__win = XCreateWindow(term->__dsp, term->win.root, 0, 0, - term->cols * term->win.font_width, term->rows * term->win.font_height, - 0, DefaultDepth(term->__dsp, term->__scr), CopyFromParent, - DefaultVisual(term->__dsp, term->__scr), - CWBackPixmap | CWEventMask, &wa); + return XftColorAllocName(x->dsp, x->vis, x->cmap, name, col); +} - XMapWindow(term->__dsp, term->__win); - term->win.termgc = XCreateGC(term->__dsp, term->__win, 0, NULL); +static void x_window_event_init(x_window_t *x) +{ + x->attrs.background_pixel = x->dc.color[DEFAULT_BG].pixel; + x->attrs.border_pixel = x->dc.color[DEFAULT_BG].pixel; + x->attrs.bit_gravity = NorthWestGravity; + x->attrs.event_mask = + FocusChangeMask | KeyPressMask | ExposureMask | VisibilityChangeMask | + StructureNotifyMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + x->attrs.colormap = x->cmap; +} - wm_name = XInternAtom(term->__dsp, "_NET_WM_NAME", False); - XChangeProperty(term->__dsp, term->__win, wm_name, - XInternAtom(term->__dsp, "UTF8_STRING", False), 8, PropModeReplace, - (unsigned char *)term->title, strlen(term->title)); +static void x_window_dc_init(x_window_t *x, Window *parent) +{ + XGCValues val = { 0 }; - XSync(term->__dsp, False); + val.graphics_exposures = False; + x->dc.gc = XCreateGC(x->dsp, *parent, GCGraphicsExposures, &val); } -static void term_redraw(term_t *term) +static bool x_window_render_init(x_window_t *x, int cols) { - char buf[1]; + x->buf = XCreatePixmap(x->dsp, x->win, x->term.w, x->term.h, + DefaultDepth(x->dsp, x->screen)); + + XSetForeground(x->dsp, x->dc.gc, x->dc.color[DEFAULT_BG].pixel); + XFillRectangle(x->dsp, x->buf, x->dc.gc, 0, 0, x->term.w, x->term.h); - XSetForeground(term->__dsp, term->win.termgc, term->win.bg); - XFillRectangle(term->__dsp, term->__win, term->win.termgc, 0, 0, - term->cols * term->win.font_width, term->rows * term->win.font_height); + x->specbuf = malloc(cols * sizeof(XftGlyphFontSpec)); + if (x->specbuf == NULL) + return false; - XSetForeground(term->__dsp, term->win.termgc, term->win.fg); + x->draw = XftDrawCreate(x->dsp, x->buf, x->vis, x->cmap); + return true; +} - for (uint32_t y = 0; y < term->rows; y++) +static bool x_window_input_init(x_window_t *x) +{ + static const char *locales[] = { - for (uint32_t x = 0; x < term->cols; x++) + "@im=local", + "@im", + "@im=none" + }; + + x->xim = XOpenIM(x->dsp, NULL, NULL, NULL); + if (x->xim == NULL) + { + STATIC_ARRAY_FOREACH(const char **locale, locales) { - buf[0] = term->buf[y * term->cols + x]; - if (!iscntrl(buf[0])) - { - XDrawString(term->__dsp, term->__win, term->win.termgc, - x * term->win.font_width, - y * term->win.font_height + term->win.font->ascent, - buf, 1); - } + XSetLocaleModifiers(*locale); + x->xim = XOpenIM(x->dsp, NULL, NULL, NULL); + if (x->xim != NULL) + goto xim_ok; } + + ERROR("XOpenIM failed, could not open input device"); + return false; } - XSetForeground(term->__dsp, term->win.termgc, term->win.fg); - XFillRectangle(term->__dsp, term->__win, term->win.termgc, - term->__x * term->win.font_width, - term->__y * term->win.font_height, - term->win.font_width, term->win.font_height); - XSync(term->__dsp, False); +xim_ok: + x->xic = XCreateIC(x->xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, x->win, XNFocusWindow, x->win, NULL + ); + + if (x->xic != NULL) + return true; + + ERROR("XCreateIC failed, could not obtain input method"); + return false; } -static void term_buf(term_t *term, char *buf, size_t size) +static void x_window_cursor_init(x_window_t *x) { - static bool just_wrapped = false; + Cursor cursor; + XColor mouse_fg; + XColor mouse_bg; - for (size_t i = 0; i < size; i++) - { - if (buf[i] == '\r') - term->__x = 0; - else - { - if (buf[i] != '\n') - { - term->buf[term->__y * term->cols + term->__x] = buf[i]; - term->__x++; - - if (term->__x >= term->cols) - { - term->__x = 0; - term->__y++; - just_wrapped = true; - } - else - just_wrapped = false; - } - else if (!just_wrapped) - { - term->__y++; - just_wrapped = false; - } + cursor = XCreateFontCursor(x->dsp, XC_xterm); + XDefineCursor(x->dsp, x->win, cursor); - if (term->__y >= term->rows) - { - memmove(term->buf, &term->buf[term->cols], term->cols * (term->rows - 1)); - term->__y = term->rows - 1; + if (XParseColor(x->dsp, x->cmap, __color_name[MOUSE_FG], &mouse_fg) == 0) + { + mouse_fg.red = 0xFFFF; + mouse_fg.green = 0xFFFF; + mouse_fg.blue = 0xFFFF; + } - for (uint32_t i = 0; i < term->cols; i++) - term->buf[term->__y * term->cols + i] = 0; - } - } + if (XParseColor(x->dsp, x->cmap, __color_name[MOUSE_BG], &mouse_bg) == 0) + { + mouse_bg.red = 0x0000; + mouse_bg.green = 0x0000; + mouse_bg.blue = 0x0000; } + + XRecolorCursor(x->dsp, cursor, &mouse_fg, &mouse_bg); } -static void term_key(term_t *term, XKeyEvent *ev) +static void x_window_property_init(x_window_t *x, pid_t *pid) { - char buf[32]; - int num; - KeySym ksym; + x->xembed = XInternAtom(x->dsp, "_XEMBED", False); + x->wm_delete_win = XInternAtom(x->dsp, "WM_DELETE_WINDOW", False); + x->net_wm_name = XInternAtom(x->dsp, "_NET_WM_NAME", False); - num = XLookupString(ev, buf, sizeof(buf), &ksym, 0); + XSetWMProtocols(x->dsp, x->win, &x->wm_delete_win, 1); - write(term->pty.master, buf, num); + x->net_wm_pid = XInternAtom(x->dsp, "_NET_WM_PID", False); + XChangeProperty(x->dsp, x->win, x->net_wm_pid, XA_CARDINAL, 32, + PropModeReplace, (u_char *)pid, 1); } -#define ACTION_FPS 30 -#define XFPS 120 - -#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec) * 1000 + (t1.tv_nsec - t2.tv_nsec) / 1E6) -static void fps_redraw(term_t *term, fd_set *readable, struct timespec *tv) +static bool x_window_color_init(x_window_t *x) { - static struct timespec last = { 0 }; - static int xev = ACTION_FPS; - XEvent ev; - struct timespec now; - bool dodraw = false; + static bool loaded = false; - if (last.tv_sec == 0 && last.tv_nsec == 0) + x->dc.col_len = MAX(COUNT_OF(__color_name), 256); + x->dc.color = malloc(x->dc.col_len * sizeof(XftColor)); + + if (x->dc.color == NULL) { - clock_gettime(CLOCK_MONOTONIC, &last); + ERROR("Could not allocate memory for color array"); + return false; } - if (FD_ISSET(term->win.fd, readable)) - xev = ACTION_FPS; - - clock_gettime(CLOCK_MONOTONIC, &now); - tv->tv_sec = 0; - tv->tv_nsec = (1000 * 1E6) / XFPS; - - if (TIMEDIFF(now, last) > 1000 / (xev ? XFPS : ACTION_FPS)) + if (loaded) { - dodraw = true; - last = now; + for (XftColor *cp = x->dc.color; cp < &(x->dc.color[x->dc.col_len]); cp++) + XftColorFree(x->dsp, x->vis, x->cmap, cp); } - if (dodraw) + for (size_t i = 0; i < x->dc.col_len; i++) { - while (XPending(term->__dsp)) + if (!x_window_load_color(x, i, NULL, &x->dc.color[i])) { - XNextEvent(term->__dsp, &ev); - if (XFilterEvent(&ev, None)) - continue; - - switch (ev.type) - { - case KeyPress: - term_key(term, &ev.xkey); - break; - } + ERROR("Could not allocate color %s (%zu)", __color_name[i], i); + return false; } + } + loaded = true; - term_redraw(term); - XFlush(term->__dsp); + return true; +} - if (xev && !FD_ISSET(term->win.fd, readable)) - xev--; +bool x_window_init(x_window_t *x, int cols, int rows) +{ + pid_t pid = getpid(); + Window parent; - if (!FD_ISSET(term->pty.master, readable) && !FD_ISSET(term->win.fd, readable)) - { - tv->tv_sec = 0; - tv->tv_nsec = 0; - } + x->dsp = XOpenDisplay(NULL); + if (x->dsp == NULL) + { + ERROR("Can't open display"); + return false; } + + if (!FcInit()) + { + ERROR("Could not init fontconfig"); + return false; + } + + x->screen = XDefaultScreen(x->dsp); + x->vis = XDefaultVisual(x->dsp, x->screen); + + if (!fonts_load(x, "Inconsolata", 12)) + goto error; + + x->cmap = XDefaultColormap(x->dsp, x->screen); + + if (!x_window_color_init(x)) + goto error; + + x->term.w = 2 * BORDER_PX + cols * x->term.cw; + x->term.h = 2 * BORDER_PX + rows * x->term.ch; + + if (x->gm & XNegative) + x->l = DisplayWidth(x->dsp, x->screen) - x->term.w - 2; + if (x->gm & YNegative) + x->t = DisplayHeight(x->dsp, x->screen) - x->term.h - 2; + + x_window_event_init(x); + + parent = XRootWindow(x->dsp, x->screen); + + /* Create actual window */ + x->win = XCreateWindow( + x->dsp, + parent, + x->l, + x->t, + x->term.w, + x->term.h, + 0, + XDefaultDepth(x->dsp, x->screen), + InputOutput, + x->vis, + CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, + &x->attrs + ); + + x_window_dc_init(x, &parent); + + if (!x_window_render_init(x, cols)) + goto error; + + if (!x_window_input_init(x)) + goto error; + + x_window_cursor_init(x); + x_window_property_init(x, &pid); + + x->term.mode = MODE_NUMLOCK; + XMapWindow(x->dsp, x->win); + x_window_hints(x); + + XSync(x->dsp, False); + return true; + +error: + x_window_dtr(x); + return false; } -#define BUF_SIZE 2048 -void term_run(term_t *term) +void x_window_dtr(x_window_t *x) { - int maxfd; - size_t size; - fd_set readable; - char buf[BUF_SIZE]; - struct timespec timeout = { 0 }; + for (XftColor *cp = x->dc.color; cp < &(x->dc.color[x->dc.col_len]); cp++) + XftColorFree(x->dsp, x->vis, x->cmap, cp); - maxfd = MAX(term->pty.master, term->win.fd); + free(x->dc.color); + XFree(x->draw); + XFree(x->dc.gc); + free(x->specbuf); - while (true) - { - FD_ZERO(&readable); - FD_SET(term->pty.master, &readable); - FD_SET(term->win.fd, &readable); + fonts_unload(x); - assert(pselect(maxfd + 1, &readable, NULL, NULL, &timeout, NULL) != -1); + XDestroyWindow(x->dsp, x->win); + XCloseDisplay(x->dsp); +} - if (FD_ISSET(term->pty.master, &readable)) - { - size = read(term->pty.master, buf, sizeof(buf)); - term_buf(term, buf, size); - } +void x_window_hints(x_window_t *x) +{ + XSizeHints *size = NULL; + XClassHint class = { + TERM_NAME, + TERM_NAME + }; + XWMHints wm = { + .flags = InputHint, + .input = 1 + }; + + size = XAllocSizeHints(); + /* PANIC! here */ + assert(size != NULL); + + size->flags = PSize | PResizeInc | PBaseSize; + size->height = x->term.h; + size->width = x->term.w; + size->height_inc = x->term.ch; + size->width_inc = x->term.cw; + size->base_height = 2 * BORDER_PX; + size->base_width = 2 * BORDER_PX; - fps_redraw(term, &readable, &timeout); + if (x->is_fixed) + { + size->flags |= PMaxSize | PMinSize; + size->min_width = size->max_width = x->term.w; + size->min_height = size->max_height = x->term.h; } + + if (x->gm & (XValue | YValue)) + { + size->flags |= USPosition | PWinGravity; + size->x = x->l; + size->y = x->t; + size->win_gravity = x_geo_mask_to_gravity(x->gm); + } + + XSetWMProperties(x->dsp, x->win, NULL, NULL, NULL, 0, size, &wm, &class); + XFree(size); } diff --git a/x.h b/x.h @@ -1,6 +1,87 @@ #ifndef _X_H #define _X_H -void term_xinit(term_t *term); +#include <X11/Xlib.h> +#include <X11/Xft/Xft.h> + +#include <stdbool.h> + +typedef struct x_window_s x_window_t; + +#include "font.h" + +struct x_window_s { + Display *dsp; /*!< Display context */ + Colormap cmap; /*!< Color Map */ + Window win; /*!< Main Window */ + Drawable buf; /*!< Drawing buffer */ + XftDraw *draw; /*!< Drawing context */ + Visual *vis; /*!< Visual context */ + XSetWindowAttributes attrs; /*!< Window attributes */ + + XftGlyphFontSpec *specbuf; /*!< Font context */ + Atom xembed; /*!< Embed property */ + Atom wm_delete_win; /*!< Delete window property */ + Atom net_wm_name; /*!< Window name property */ + Atom net_wm_pid; /*!< Window pid property */ + + XIM xim; /*!< X input method */ + XIC xic; /*!< X input context */ + + int screen; /*!< Internal screen ID */ + bool is_fixed; /*!< Is fixed geometry ? */ + int l; /*!< Left offset */ + int t; /*!< Top offset */ + int gm; /*!< Geometry mask */ + + struct { + int tw; /*!< TTY width */ + int th; /*!< TTY height */ + int w; /*!< Window width */ + int h; /*!< Window height */ + int ch; /*!< Char height */ + int cw; /*!< Char width */ + int mode; /*!< Window flags */ + int cursor; /*!< Cursor style */ + } term; + + struct { + XftColor *color; + size_t col_len; + double used_font_size; + font_t font; + font_t bfont; + font_t ifont; + font_t ibfont; + GC gc; + } dc; +}; + +enum window_mode { + MODE_VISIBLE = 1 << 0, + MODE_FOCUSED = 1 << 1, + MODE_APPKEYPAD = 1 << 2, + MODE_MOUSEBTN = 1 << 3, + MODE_MOUSEMOTION = 1 << 4, + MODE_REVERSE = 1 << 5, + MODE_KBDLOCK = 1 << 6, + MODE_HIDE = 1 << 7, + MODE_APPCURSOR = 1 << 8, + MODE_MOUSESGR = 1 << 9, + MODE_8BIT = 1 << 10, + MODE_BLINK = 1 << 11, + MODE_FBLINK = 1 << 12, + MODE_FOCUS = 1 << 14, + MODE_MOUSEX10 = 1 << 14, + MODE_MOUSEMANY = 1 << 15, + MODE_BRCKTPASTE = 1 << 16, + MODE_NUMLOCK = 1 << 17, + MODE_MOUSE = MODE_MOUSEBTN | MODE_MOUSEMOTION | MODE_MOUSEX10 | MODE_MOUSEMANY +}; + +bool x_window_init(x_window_t *x, int cols, int rows); +void x_window_dtr(x_window_t *x); +void x_window_resize(x_window_t *x, int cols, int rows); +void x_window_hints(x_window_t *x); #endif /* _X_H */