commit 78088f20cdb93ead1b980f3a4092ce0a1227aafa
Author: lumidify <nobody@lumidify.org>
Date: Thu, 1 Apr 2021 19:31:23 +0200
Add initial work that was done without version control
Diffstat:
A | .gitignore | | | 4 | ++++ |
A | Makefile | | | 24 | ++++++++++++++++++++++++ |
A | ledit.c | | | 859 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 887 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,4 @@
+tmp
+ledit
+*.core
+*.o
diff --git a/Makefile b/Makefile
@@ -0,0 +1,24 @@
+.POSIX:
+
+NAME = ledit
+VERSION = -999-prealpha0
+
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/man
+
+BIN = ${NAME}
+SRC = ${BIN:=.c}
+MAN1 = ${BIN:=.1}
+
+CFLAGS = -g -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11 xkbfile pangoxft xext`
+LDFLAGS += `pkg-config --libs x11 xkbfile pangoxft xext` -lm
+
+all: ${BIN}
+
+.c:
+ ${CC} ${CFLAGS} ${LDFLAGS} -o $@ $<
+
+clean:
+ rm -f ${BIN}
+
+.PHONY: all clean
diff --git a/ledit.c b/ledit.c
@@ -0,0 +1,859 @@
+#include <math.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <locale.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+#include <X11/XF86keysym.h>
+#include <X11/cursorfont.h>
+
+#include <pango/pangoxft.h>
+
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBrules.h>
+#include <X11/extensions/Xdbe.h>
+
+static enum mode {
+ NORMAL = 1,
+ INSERT = 2,
+ VISUAL = 4
+} cur_mode = INSERT;
+
+struct key {
+ char *text; /* for keys that correspond with text */
+ KeySym keysym; /* for other keys, e.g. arrow keys */
+ enum mode modes; /* modes in which this keybinding is functional */
+ void (*func)(void); /* callback function */
+};
+
+static struct {
+ Display *dpy;
+ GC gc;
+ Window win;
+ XdbeBackBuffer back_buf;
+ Visual *vis;
+ PangoFontMap *fontmap;
+ PangoContext *context;
+ PangoFontDescription *font;
+ Colormap cm;
+ int screen;
+ int depth;
+ XIM xim;
+ XIC xic;
+ int w;
+ int h;
+ XftColor fg;
+ XftColor bg;
+
+ Atom wm_delete_msg;
+} state;
+
+static void mainloop(void);
+static void setup(int argc, char *argv[]);
+static void cleanup(void);
+static void redraw(void);
+static void drag_motion(XEvent event);
+static void resize_window(int w, int h);
+static void button_release(void);
+static void button_press(XEvent event);
+static void key_press(XEvent event);
+
+int
+main(int argc, char *argv[]) {
+ setup(argc, argv);
+ mainloop();
+ cleanup();
+
+ return 0;
+}
+
+static struct line {
+ PangoLayout *layout;
+ XftDraw *draw;
+ char *text;
+ size_t cap;
+ size_t len;
+ Pixmap pix;
+ int w;
+ int h;
+ int pix_w;
+ int pix_h;
+ char dirty;
+} *lines = NULL;
+static size_t lines_num = 0;
+static size_t lines_cap = 0;
+
+static int cur_line = 0;
+static int cur_subline = 0;
+static int cur_index = 0;
+static int trailing = 0;
+static int total_height = 0;
+static int cur_display_offset = 0;
+
+static void
+init_line(struct line *l) {
+ /* FIXME: check that layout created properly */
+ l->layout = pango_layout_new(state.context);
+ pango_layout_set_width(l->layout, (state.w - 10) * PANGO_SCALE);
+ pango_layout_set_font_description(l->layout, state.font);
+ pango_layout_set_wrap(l->layout, PANGO_WRAP_WORD_CHAR);
+ l->text = NULL;
+ l->cap = l->len = 0;
+ l->pix = None;
+ /* FIXME: does this set line height reasonably when no text yet? */
+ pango_layout_get_pixel_size(l->layout, &l->w, &l->h);
+ l->dirty = 1;
+}
+
+static void recalc_height_absolute(void);
+
+static void
+insert_text(struct line *l, int index, char *text, int len) {
+ if (len == -1)
+ len = strlen(text);
+ if (l->len + len > l->cap) {
+ l->cap *= 2;
+ if (l->cap == 0)
+ l->cap = 2;
+ l->text = realloc(l->text, l->cap);
+ if (!l->text) exit(1);
+ }
+ memmove(l->text + index + len, l->text + index, l->len - index);
+ memcpy(l->text + index, text, len);
+ l->len += len;
+ pango_layout_set_text(l->layout, l->text, l->len);
+ recalc_height_absolute();
+ l->dirty = 1;
+}
+
+static void insert_line_entry(int index);
+
+static void
+render_line(struct line *l) {
+ /* FIXME: check for <= 0 on size */
+ if (l->pix == None) {
+ l->pix = XCreatePixmap(state.dpy, state.back_buf, l->w + 10, l->h + 10, state.depth);
+ l->pix_w = l->w + 10;
+ l->pix_h = l->h + 10;
+ l->draw = XftDrawCreate(state.dpy, l->pix, state.vis, state.cm);
+ } else if (l->pix_w < l->w || l->pix_h < l->h) {
+ int new_w = l->w > l->pix_w ? l->w + 10 : l->pix_w + 10;
+ int new_h = l->h > l->pix_h ? l->h + 10 : l->pix_h + 10;
+ XFreePixmap(state.dpy, l->pix);
+ l->pix = XCreatePixmap(state.dpy, state.back_buf, new_w, new_h, state.depth);
+ l->pix_w = new_w;
+ l->pix_h = new_h;
+ XftDrawChange(l->draw, l->pix);
+ }
+ XftDrawRect(l->draw, &state.bg, 0, 0, l->w, l->h);
+ pango_xft_render_layout(l->draw, &state.fg, l->layout, 0, 0);
+ l->dirty = 0;
+}
+
+static void
+append_line(int text_index, int line_index) {
+ if (lines_num >= lines_cap) {
+ lines_cap *= 2;
+ if (lines_cap == 0)
+ lines_cap = 2;
+ lines = realloc(lines, lines_cap * sizeof(struct line));
+ if (!lines) exit(1);
+ }
+ memmove(lines + line_index + 2, lines + line_index + 1, (lines_num - (line_index + 1)) * sizeof(struct line));
+ struct line *new_l = &lines[line_index + 1];
+ init_line(new_l);
+ lines_num++;
+ if (text_index != -1) {
+ struct line *l = &lines[line_index];
+ int len = l->len - text_index;
+ new_l->pix = None;
+ new_l->len = len;
+ new_l->cap = len + 10;
+ new_l->text = malloc(new_l->cap);
+ if (!new_l->text) exit(1);
+ memcpy(new_l->text, l->text + text_index, len);
+ l->len = text_index;
+ pango_layout_set_text(new_l->layout, new_l->text, new_l->len);
+ pango_layout_set_text(l->layout, l->text, l->len);
+ /* FIXME: set height here */
+ }
+}
+
+static void change_keyboard(char *lang);
+
+PangoAttrList *basic_attrs;
+
+static void
+mainloop(void) {
+ XEvent event;
+ int xkb_event_type;
+ int major, minor;
+ if (!XkbQueryExtension(state.dpy, 0, &xkb_event_type, NULL, &major, &minor)) {
+ fprintf(stderr, "XKB not supported.");
+ exit(1);
+ }
+ printf("XKB (%d.%d) supported.\n", major, minor);
+ /* This should select the events when the keyboard mapping changes.
+ When e.g. 'setxkbmap us' is executed, two events are sent, but I haven't figured out how to
+ change that. When the xkb layout switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
+ this issue does not occur because only a state event is sent. */
+ XkbSelectEvents(state.dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask);
+ XkbSelectEventDetails(state.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
+ XSync(state.dpy, False);
+ int running = 1;
+ int change_kbd = 0;
+
+
+ /*draw = XftDrawCreate(state.dpy, state.back_buf, state.vis, state.cm);*/
+ state.fontmap = pango_xft_get_font_map(state.dpy, state.screen);
+ state.context = pango_font_map_create_context(state.fontmap);
+
+ state.font = pango_font_description_from_string("Monospace");
+ pango_font_description_set_size(state.font, 16 * PANGO_SCALE);
+
+ basic_attrs = pango_attr_list_new();
+ PangoAttribute *no_hyphens = pango_attr_insert_hyphens_new(FALSE);
+ pango_attr_list_insert(basic_attrs, no_hyphens);
+
+ append_line(-1, -1);
+
+ XftColorAllocName(state.dpy, state.vis, state.cm, "#000000", &state.fg);
+ XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg);
+ int need_redraw = 0;
+ redraw();
+
+ while (running) {
+ do {
+ XNextEvent(state.dpy, &event);
+ if (event.type == xkb_event_type) {
+ change_kbd = 1;
+ continue;
+ }
+ if (XFilterEvent(&event, None))
+ continue;
+ switch (event.type) {
+ case Expose:
+ redraw();
+ need_redraw = 1;
+ break;
+ case ConfigureNotify:
+ resize_window(event.xconfigure.width, event.xconfigure.height);
+ if (cur_display_offset > 0 && cur_display_offset + state.h >= total_height) {
+ cur_display_offset = total_height - state.h;
+ if (cur_display_offset < 0)
+ cur_display_offset = 0;
+ }
+ redraw();
+ need_redraw = 1;
+ break;
+ case ButtonPress:
+ switch (event.xbutton.button) {
+ case Button1:
+ button_press(event);
+ break;
+ case Button4:
+ cur_display_offset -= 10;
+ if (cur_display_offset < 0)
+ cur_display_offset = 0;
+ break;
+ case Button5:
+ if (cur_display_offset + state.h < total_height) {
+ cur_display_offset += 10;
+ if (cur_display_offset + state.h > total_height)
+ cur_display_offset = total_height - state.h;
+ }
+ break;
+ }
+ need_redraw = 1;
+ break;
+ case ButtonRelease:
+ if (event.xbutton.button == Button1)
+ button_release();
+ break;
+ case MotionNotify:
+ drag_motion(event);
+ break;
+ case KeyPress:
+ need_redraw = 1;
+ key_press(event);
+ break;
+ case ClientMessage:
+ if ((Atom)event.xclient.data.l[0] == state.wm_delete_msg)
+ running = 0;
+ default:
+ break;
+ }
+ } while (XPending(state.dpy));
+
+ if (change_kbd) {
+ change_kbd = 0;
+ XkbStateRec s;
+ XkbGetState(state.dpy, XkbUseCoreKbd, &s);
+ XkbDescPtr desc = XkbGetKeyboard(state.dpy, XkbAllComponentsMask, XkbUseCoreKbd);
+ char *group = XGetAtomName(state.dpy, desc->names->groups[s.group]);
+ change_keyboard(group);
+ /*char *symbols = XGetAtomName(state.dpy, desc->names->symbols);*/
+ XFree(group);
+ /*XFree(symbols);*/
+ XkbFreeKeyboard(desc, XkbAllComponentsMask, True);
+ }
+ if (need_redraw) {
+ XSetForeground(state.dpy, state.gc, state.bg.pixel);
+ XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
+ int h = 0;
+ /*int cur_line_height = 0;*/
+ int tmp_w, tmp_h;
+ int cur_line_y = 0;
+ int cursor_displayed = 0;
+ for (int i = 0; i < lines_num; i++) {
+ if (lines[i].dirty) {
+ if (i == cur_line && cur_mode == NORMAL) {
+ PangoAttribute *attr0 = pango_attr_background_new(0, 0, 0);
+ PangoAttribute *attr1 = pango_attr_foreground_new(65535, 65535, 65535);
+ attr0->start_index = cur_index;
+ attr0->end_index = cur_index + 1;
+ attr1->start_index = cur_index;
+ attr1->end_index = cur_index + 1;
+ PangoAttribute *attr2 = pango_attr_insert_hyphens_new(FALSE);
+ PangoAttrList *list = pango_attr_list_new();
+ pango_attr_list_insert(list, attr0);
+ pango_attr_list_insert(list, attr1);
+ pango_attr_list_insert(list, attr2);
+ pango_layout_set_attributes(lines[cur_line].layout, list);
+ } else {
+ pango_layout_set_attributes(lines[i].layout, basic_attrs);
+ }
+ render_line(&lines[i]);
+ }
+ if (h + lines[i].h > cur_display_offset) {
+ int final_y = 0;
+ int dest_y = h - cur_display_offset;
+ int final_h = lines[i].h;
+ if (h < cur_display_offset) {
+ dest_y = 0;
+ final_y = cur_display_offset - h;
+ final_h -= cur_display_offset - h;
+ }
+ if (dest_y + final_h > state.h) {
+ final_h -= final_y + final_h - cur_display_offset - state.h;
+ }
+ XCopyArea(state.dpy, lines[i].pix, state.back_buf, state.gc, 0, final_y, lines[i].w, final_h, 0, dest_y);
+ if (i == cur_line) {
+ cur_line_y = h - cur_display_offset;
+ cursor_displayed = 1;
+ }
+ }
+ if (h + lines[i].h >= cur_display_offset + state.h)
+ break;
+ h += lines[i].h;
+ }
+ need_redraw = 0;
+
+ XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
+ PangoRectangle strong, weak;
+ pango_layout_get_cursor_pos(lines[cur_line].layout, cur_index, &strong, &weak);
+ int cursor_y = strong.y / PANGO_SCALE + cur_line_y;
+ if (cursor_displayed && cursor_y >= 0) {
+ if (cur_mode == NORMAL && cur_index == lines[cur_line].len) {
+ XFillRectangle(
+ state.dpy, state.back_buf, state.gc,
+ strong.x / PANGO_SCALE, cursor_y,
+ 10, strong.height / PANGO_SCALE
+ );
+ } else if (cur_mode == INSERT) {
+ XDrawLine(
+ state.dpy, state.back_buf, state.gc,
+ strong.x / PANGO_SCALE, cursor_y,
+ strong.x / PANGO_SCALE, (strong.y + strong.height) / PANGO_SCALE + cur_line_y
+ );
+ }
+ }
+ if (total_height > state.h) {
+ double scroll_h = ((double)state.h / total_height) * state.h;
+ double scroll_y = ((double)cur_display_offset / (total_height - state.h)) * (state.h - scroll_h);
+ XFillRectangle(state.dpy, state.back_buf, state.gc, state.w - 10, (int)round(scroll_y), 10, (int)round(scroll_h));
+ }
+
+ XdbeSwapInfo swap_info;
+ swap_info.swap_window = state.win;
+ swap_info.swap_action = XdbeBackground;
+
+ if (!XdbeSwapBuffers(state.dpy, &swap_info, 1))
+ exit(1);
+ XFlush(state.dpy);
+ }
+ }
+ pango_attr_list_unref(basic_attrs);
+}
+
+static void
+setup(int argc, char *argv[]) {
+ setlocale(LC_CTYPE, "");
+ XSetLocaleModifiers("");
+ XSetWindowAttributes attrs;
+ XGCValues gcv;
+
+ state.w = 500;
+ state.h = 500;
+ state.dpy = XOpenDisplay(NULL);
+ state.screen = DefaultScreen(state.dpy);
+
+ /* based on http://wili.cc/blog/xdbe.html */
+ int major, minor;
+ if (XdbeQueryExtension(state.dpy, &major, &minor)) {
+ printf("Xdbe (%d.%d) supported, using double buffering.\n", major, minor);
+ int num_screens = 1;
+ Drawable screens[] = { DefaultRootWindow(state.dpy) };
+ XdbeScreenVisualInfo *info = XdbeGetVisualInfo(state.dpy, screens, &num_screens);
+ if (!info || num_screens < 1 || info->count < 1) {
+ fprintf(stderr, "No visuals support Xdbe.\n");
+ exit(1);
+ }
+ XVisualInfo xvisinfo_templ;
+ xvisinfo_templ.visualid = info->visinfo[0].visual; // We know there's at least one
+ xvisinfo_templ.screen = 0;
+ xvisinfo_templ.depth = info->visinfo[0].depth;
+ int matches;
+ XVisualInfo *xvisinfo_match =
+ XGetVisualInfo(state.dpy, VisualIDMask|VisualScreenMask|VisualDepthMask, &xvisinfo_templ, &matches);
+ if (!xvisinfo_match || matches < 1) {
+ fprintf(stderr, "Couldn't match a Visual with double buffering\n");
+ exit(1);
+ }
+ state.vis = xvisinfo_match->visual;
+ } else {
+ fprintf(stderr, "No Xdbe support.\n");
+ exit(1);
+ }
+
+ state.depth = DefaultDepth(state.dpy, state.screen);
+ state.cm = DefaultColormap(state.dpy, state.screen);
+
+ memset(&attrs, 0, sizeof(attrs));
+ attrs.background_pixmap = None;
+ attrs.colormap = state.cm;
+ state.win = XCreateWindow(state.dpy, DefaultRootWindow(state.dpy), 0, 0,
+ state.w, state.h, 0, state.depth,
+ InputOutput, state.vis, CWBackPixmap | CWColormap, &attrs);
+
+ state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeBackground);
+
+ memset(&gcv, 0, sizeof(gcv));
+ gcv.line_width = 1;
+ state.gc = XCreateGC(state.dpy, state.back_buf, GCLineWidth, &gcv);
+
+ XSelectInput(state.dpy, state.win, StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask);
+
+ state.wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False);
+ XSetWMProtocols(state.dpy, state.win, &state.wm_delete_msg, 1);
+
+ /* blatantly stolen from st (simple terminal) */
+ if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
+ XSetLocaleModifiers("@im=local");
+ if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
+ XSetLocaleModifiers("@im=");
+ if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
+ fprintf(stderr, "XOpenIM failed. Could not open input device.\n");
+ exit(1);
+ }
+ }
+ }
+ state.xic = XCreateIC(state.xim, XNInputStyle, XIMPreeditNothing
+ | XIMStatusNothing, XNClientWindow, state.win,
+ XNFocusWindow, state.win, NULL);
+ if (state.xic == NULL) {
+ fprintf(stderr, "XCreateIC failed. Could not obtain input method.\n");
+ exit(1);
+ }
+ XSetICFocus(state.xic);
+
+ XMapWindow(state.dpy, state.win);
+ redraw();
+}
+
+static void
+cleanup(void) {
+ XDestroyWindow(state.dpy, state.win);
+ XCloseDisplay(state.dpy);
+}
+
+static void
+redraw(void) {
+ XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
+ XFillRectangle(state.dpy, state.back_buf, state.gc, 0, 0, state.w, state.h);
+}
+
+static void
+button_press(XEvent event) {
+}
+
+static void
+button_release(void) {
+}
+
+static void
+recalc_height(void) {
+ /*
+ int w, h;
+ pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
+ total_height += (h - cur_line_height);
+ */
+ if (total_height < 0)
+ total_height = 0; /* should never actually happen */
+ /*cur_line_height = h;*/
+}
+
+static void
+set_cur_line_height(void) {
+ int w, h;
+ pango_layout_get_pixel_size(lines[cur_line].layout, &w, &h);
+ lines[cur_line].h = h;
+ /*cur_line_height = h;*/
+}
+
+static void
+recalc_height_absolute(void) {
+ int w, h;
+ total_height = 0;
+ for (int i = 0; i < lines_num; i++) {
+ pango_layout_get_pixel_size(lines[i].layout, &w, &h);
+ total_height += h;
+ lines[i].w = w;
+ lines[i].h = h;
+ }
+ set_cur_line_height();
+}
+
+static void
+resize_window(int w, int h) {
+ state.w = w;
+ state.h = h;
+ total_height = 0;
+ int tmp_w, tmp_h;
+ for (int i = 0; i < lines_num; i++) {
+ /* 10 pixels for scrollbar */
+ pango_layout_set_width(lines[i].layout, (w - 10) * PANGO_SCALE);
+ pango_layout_get_pixel_size(lines[i].layout, &tmp_w, &tmp_h);
+ total_height += tmp_h;
+ lines[i].h = tmp_h;
+ lines[i].dirty = 1;
+ }
+ //set_cur_line_height();
+}
+
+static void
+drag_motion(XEvent event) {
+}
+
+static void
+delete_line_entry(int index) {
+ if (index < lines_num - 1)
+ memmove(lines + index, lines + index + 1, (lines_num - index - 1) * sizeof(struct line));
+ lines_num--;
+}
+
+static void
+backspace(void) {
+ if (cur_index == 0) {
+ if (cur_line != 0) {
+ struct line *l1 = &lines[cur_line - 1];
+ struct line *l2 = &lines[cur_line];
+ int old_len = l1->len;
+ insert_text(l1, l1->len, l2->text, l2->len);
+ delete_line_entry(cur_line);
+ cur_line--;
+ cur_index = old_len;
+ //total_height -= cur_line_height();
+ //set_cur_line_height();
+ }
+ } else {
+ int i = cur_index - 1;
+ struct line *l = &lines[cur_line];
+ /* find valid utf8 char - this probably needs to be improved */
+ while (i > 0 && ((l->text[i] & 0xC0) == 0x80))
+ i--;
+ memmove(l->text + i, l->text + cur_index, l->len - cur_index);
+ l->len -= cur_index - i;
+ cur_index = i;
+ pango_layout_set_text(l->layout, l->text, l->len);
+ }
+ lines[cur_line].dirty = 1;
+ recalc_height_absolute();
+}
+
+static void
+delete_key(void) {
+ if (cur_index == lines[cur_line].len) {
+ if (cur_line != lines_num - 1) {
+ struct line *l1 = &lines[cur_line];
+ struct line *l2 = &lines[cur_line + 1];
+ int old_len = l1->len;
+ insert_text(l1, l1->len, l2->text, l2->len);
+ delete_line_entry(cur_line + 1);
+ cur_index = old_len;
+ /*total_height -= cur_line_height();
+ set_cur_line_height();*/
+ }
+ } else {
+ int i = cur_index + 1;
+ struct line *l = &lines[cur_line];
+ while (i < lines[cur_line].len && ((lines[cur_line].text[i] & 0xC0) == 0x80))
+ i++;
+ memmove(l->text + cur_index, l->text + i, l->len - i);
+ l->len -= i - cur_index;
+ pango_layout_set_text(l->layout, l->text, l->len);
+ }
+ lines[cur_line].dirty = 1;
+ recalc_height_absolute();
+}
+
+static void
+move_cursor(int dir) {
+ int last_index = cur_index;
+ pango_layout_move_cursor_visually(lines[cur_line].layout, TRUE, cur_index, trailing, dir, &cur_index, &trailing);
+ /* we don't currently support a difference between the cursor being at
+ the end of a soft line and the beginning of the next line */
+ while (trailing > 0) {
+ trailing--;
+ cur_index++;
+ while (cur_index < lines[cur_line].len && ((lines[cur_line].text[cur_index] & 0xC0) == 0x80))
+ cur_index++;
+ }
+ if (cur_index < 0)
+ cur_index = 0;
+ /* when in normal mode, the cursor cannot be at the very end
+ of the line because it's always covering a character */
+ if (cur_index >= lines[cur_line].len) {
+ if (cur_mode == NORMAL)
+ cur_index = last_index;
+ else
+ cur_index = lines[cur_line].len;
+ }
+ lines[cur_line].dirty = 1;
+}
+
+static void
+cursor_left(void) {
+ move_cursor(-1);
+}
+
+static void
+cursor_right(void) {
+ move_cursor(1);
+}
+
+static void
+return_key(void) {
+ append_line(cur_index, cur_line);
+ lines[cur_line].dirty = 1;
+ cur_line++;
+ lines[cur_line].dirty = 1;
+ cur_index = 0;
+ recalc_height_absolute();
+}
+
+static void
+escape_key(void) {
+ cur_mode = NORMAL;
+ PangoDirection dir = PANGO_DIRECTION_RTL;
+ int tmp_index = cur_index;
+ if (cur_index >= lines[cur_line].len)
+ tmp_index--;
+ if (tmp_index >= 0)
+ dir = pango_layout_get_direction(lines[cur_line].layout, tmp_index);
+ if (dir == PANGO_DIRECTION_RTL || dir == PANGO_DIRECTION_WEAK_RTL) {
+ cursor_right();
+ } else {
+ cursor_left();
+ }
+ lines[cur_line].dirty = 1;
+ /*
+ if (cur_index > 0)
+ cursor_left();
+ */
+}
+
+static void
+i_key(void) {
+ cur_mode = INSERT;
+ /*
+ for (int i = 0; i < lines_num; i++) {
+ pango_layout_set_attributes(lines[i].layout, NULL);
+ }
+ */
+ lines[cur_line].dirty = 1;
+}
+
+static void
+line_down(void) {
+ int lineno, x, trailing;
+ pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
+ int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
+ PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
+ if (lineno == maxlines - 1) {
+ lines[cur_line].dirty = 1;
+ /* move to the next hard line */
+ if (cur_line < lines_num - 1) {
+ cur_line++;
+ PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, 0);
+ if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) {
+ /* set it to *after* the last index of the line */
+ cur_index = nextline->start_index + nextline->length;
+ }
+ }
+ } else {
+ /* move to the next soft line */
+ PangoLayoutLine *nextline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno + 1);
+ if (pango_layout_line_x_to_index(nextline, x, &cur_index, &trailing) == FALSE) {
+ /* set it to *after* the last index of the line */
+ cur_index = nextline->start_index + nextline->length;
+ }
+ }
+ if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
+ cursor_left();
+ lines[cur_line].dirty = 1;
+}
+
+static void
+line_up(void) {
+ int lineno, x, trailing;
+ pango_layout_index_to_line_x(lines[cur_line].layout, cur_index, 0, &lineno, &x);
+ PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line].layout, lineno);
+ if (lineno == 0) {
+ lines[cur_line].dirty = 1;
+ /* move to the previous hard line */
+ if (cur_line > 0) {
+ cur_line--;
+ int maxlines = pango_layout_get_line_count(lines[cur_line].layout);
+ PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, maxlines - 1);
+ if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) {
+ /* set it to *after* the last index of the line */
+ cur_index = prevline->start_index + prevline->length;
+ }
+ }
+ } else {
+ /* move to the previous soft line */
+ PangoLayoutLine *prevline = pango_layout_get_line_readonly(lines[cur_line].layout, lineno - 1);
+ if (pango_layout_line_x_to_index(prevline, x, &cur_index, &trailing) == FALSE) {
+ /* set it to *after* the last index of the line */
+ cur_index = prevline->start_index + prevline->length;
+ }
+ }
+ if (cur_index > 0 && cur_mode == NORMAL && cur_index >= lines[cur_line].len)
+ cursor_left();
+ lines[cur_line].dirty = 1;
+}
+
+static void
+zero_key(void) {
+ cur_index = 0;
+ lines[cur_line].dirty = 1;
+}
+
+static struct key keys_en[] = {
+ {NULL, XK_BackSpace, INSERT, &backspace},
+ {NULL, XK_Left, INSERT|NORMAL, &cursor_left},
+ {NULL, XK_Right, INSERT|NORMAL, &cursor_right},
+ {NULL, XK_Up, INSERT|NORMAL, &line_up},
+ {NULL, XK_Down, INSERT|NORMAL, &line_down},
+ {NULL, XK_Return, INSERT, &return_key},
+ {NULL, XK_Delete, INSERT, &delete_key},
+ {NULL, XK_Escape, INSERT, &escape_key},
+ {"i", 0, NORMAL, &i_key},
+ {"h", 0, NORMAL, &cursor_left},
+ {"l", 0, NORMAL, &cursor_right},
+ {"j", 0, NORMAL, &line_down},
+ {"k", 0, NORMAL, &line_up},
+ {"0", 0, NORMAL, &zero_key}
+};
+
+static struct key keys_ur[] = {
+ {NULL, XK_BackSpace, INSERT, &backspace},
+ {NULL, XK_Left, INSERT|NORMAL, &cursor_left},
+ {NULL, XK_Right, INSERT|NORMAL, &cursor_right},
+ {NULL, XK_Up, INSERT|NORMAL, &line_up},
+ {NULL, XK_Down, INSERT|NORMAL, &line_down},
+ {NULL, XK_Return, INSERT, &return_key},
+ {NULL, XK_Delete, INSERT, &delete_key},
+ {NULL, XK_Escape, INSERT, &escape_key},
+ {"ی", 0, NORMAL, &i_key},
+ {"ح", 0, NORMAL, &cursor_left},
+ {"ل", 0, NORMAL, &cursor_right},
+ {"ج", 0, NORMAL, &line_down},
+ {"ک", 0, NORMAL, &line_up},
+ {"0", 0, NORMAL, &zero_key}
+};
+
+static struct key keys_hi[] = {
+ {NULL, XK_BackSpace, INSERT, &backspace},
+ {NULL, XK_Left, INSERT|NORMAL, &cursor_left},
+ {NULL, XK_Right, INSERT|NORMAL, &cursor_right},
+ {NULL, XK_Up, INSERT|NORMAL, &line_up},
+ {NULL, XK_Down, INSERT|NORMAL, &line_down},
+ {NULL, XK_Return, INSERT, &return_key},
+ {NULL, XK_Delete, INSERT, &delete_key},
+ {NULL, XK_Escape, INSERT, &escape_key},
+ {"ि", 0, NORMAL, &i_key},
+ {"ह", 0, NORMAL, &cursor_left},
+ {"ल", 0, NORMAL, &cursor_right},
+ {"ज", 0, NORMAL, &line_down},
+ {"क", 0, NORMAL, &line_up},
+ {"0", 0, NORMAL, &zero_key}
+};
+
+#define LENGTH(X) (sizeof X / sizeof X[0])
+
+struct lang_keys {
+ char *lang;
+ struct key *keys;
+ int num_keys;
+};
+
+static struct lang_keys keys[] = {
+ {"English (US)", keys_en, LENGTH(keys_en)},
+ {"German", keys_en, LENGTH(keys_en)},
+ {"Urdu (Pakistan)", keys_ur, LENGTH(keys_ur)},
+ {"Hindi (Bolnagri)", keys_hi, LENGTH(keys_hi)}
+};
+
+static struct lang_keys *cur_keys = &keys[0];
+
+static void change_keyboard(char *lang) {
+ printf("%s\n", lang);
+ for (int i = 0; i < LENGTH(keys); i++) {
+ if (!strcmp(keys[i].lang, lang)) {
+ cur_keys = &keys[i];
+ break;
+ }
+ }
+}
+
+static void
+key_press(XEvent event) {
+ XWindowAttributes attrs;
+ char buf[32];
+ KeySym sym;
+ /* FIXME: X_HAVE_UTF8_STRING See XmbLookupString(3) */
+ int n = Xutf8LookupString(state.xic, &event.xkey, buf, sizeof(buf), &sym, NULL);
+ int found = 0;
+ for (int i = 0; i < cur_keys->num_keys; i++) {
+ if (cur_keys->keys[i].text) {
+ if (n > 0 && (cur_keys->keys[i].modes & cur_mode) && !strncmp(cur_keys->keys[i].text, buf, n)) {
+ cur_keys->keys[i].func();
+ found = 1;
+ }
+ } else if ((cur_keys->keys[i].modes & cur_mode) && cur_keys->keys[i].keysym == sym) {
+ cur_keys->keys[i].func();
+ found = 1;
+ }
+ }
+ if (cur_mode == INSERT && !found && n > 0) {
+ insert_text(&lines[cur_line], cur_index, buf, n);
+ cur_index += n;
+ }
+}