tAdd initial work that was done without version control - ledit - Text editor (… | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 78088f20cdb93ead1b980f3a4092ce0a1227aafa | |
Author: lumidify <[email protected]> | |
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 | |
t@@ -0,0 +1,4 @@ | |
+tmp | |
+ledit | |
+*.core | |
+*.o | |
diff --git a/Makefile b/Makefile | |
t@@ -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 pangoxf… | |
+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 | |
t@@ -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… | |
+ 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… | |
+ 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 - (… | |
+ 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, &m… | |
+ 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 ha… | |
+ change that. When the xkb layout switching is used (e.g. 'setxkbmap… | |
+ this issue does not occur because only a state event is sent. */ | |
+ XkbSelectEvents(state.dpy, XkbUseCoreKbd, XkbNewKeyboardNotifyMask, Xk… | |
+ XkbSelectEventDetails(state.dpy, XkbUseCoreKbd, XkbStateNotify, XkbAll… | |
+ 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.xc… | |
+ if (cur_display_offset > 0 && cur_display_offs… | |
+ cur_display_offset = total_height - st… | |
+ 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… | |
+ cur_display_offset += … | |
+ if (cur_display_offset… | |
+ cur_display_of… | |
+ } | |
+ 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_… | |
+ 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, XkbAllComp… | |
+ char *group = XGetAtomName(state.dpy, desc->names->gro… | |
+ change_keyboard(group); | |
+ /*char *symbols = XGetAtomName(state.dpy, desc->names-… | |
+ 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,… | |
+ 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 == NORMA… | |
+ PangoAttribute *attr0 = pango_… | |
+ PangoAttribute *attr1 = pango_… | |
+ attr0->start_index = cur_index; | |
+ attr0->end_index = cur_index +… | |
+ attr1->start_index = cur_index; | |
+ attr1->end_index = cur_index +… | |
+ PangoAttribute *attr2 = pango_… | |
+ PangoAttrList *list = pango_at… | |
+ pango_attr_list_insert(list, a… | |
+ pango_attr_list_insert(list, a… | |
+ pango_attr_list_insert(list, a… | |
+ pango_layout_set_attributes(li… | |
+ } else { | |
+ pango_layout_set_attributes(li… | |
+ } | |
+ 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 -… | |
+ final_h -= cur_display_offset … | |
+ } | |
+ if (dest_y + final_h > state.h) { | |
+ final_h -= final_y + final_h -… | |
+ } | |
+ XCopyArea(state.dpy, lines[i].pix, sta… | |
+ if (i == cur_line) { | |
+ cur_line_y = h - cur_display_o… | |
+ cursor_displayed = 1; | |
+ } | |
+ } | |
+ if (h + lines[i].h >= cur_display_offset + sta… | |
+ break; | |
+ h += lines[i].h; | |
+ } | |
+ need_redraw = 0; | |
+ | |
+ XSetForeground(state.dpy, state.gc, BlackPixel(state.d… | |
+ PangoRectangle strong, weak; | |
+ pango_layout_get_cursor_pos(lines[cur_line].layout, cu… | |
+ int cursor_y = strong.y / PANGO_SCALE + cur_line_y; | |
+ if (cursor_displayed && cursor_y >= 0) { | |
+ if (cur_mode == NORMAL && cur_index == lines[c… | |
+ XFillRectangle( | |
+ state.dpy, state.back_buf, state.g… | |
+ strong.x / PANGO_SCALE, cursor_y, | |
+ 10, strong.height / PANGO_SCALE | |
+ ); | |
+ } else if (cur_mode == INSERT) { | |
+ XDrawLine( | |
+ state.dpy, state.back_buf, state.g… | |
+ strong.x / PANGO_SCALE, cursor_y, | |
+ strong.x / PANGO_SCALE, (strong.y … | |
+ ); | |
+ } | |
+ } | |
+ if (total_height > state.h) { | |
+ double scroll_h = ((double)state.h / total_hei… | |
+ double scroll_y = ((double)cur_display_offset … | |
+ XFillRectangle(state.dpy, state.back_buf, stat… | |
+ } | |
+ | |
+ 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", ma… | |
+ int num_screens = 1; | |
+ Drawable screens[] = { DefaultRootWindow(state.dpy) }; | |
+ XdbeScreenVisualInfo *info = XdbeGetVisualInfo(state.dpy, scre… | |
+ 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 … | |
+ xvisinfo_templ.screen = 0; | |
+ xvisinfo_templ.depth = info->visinfo[0].depth; | |
+ int matches; | |
+ XVisualInfo *xvisinfo_match = | |
+ XGetVisualInfo(state.dpy, VisualIDMask|VisualScreenMas… | |
+ if (!xvisinfo_match || matches < 1) { | |
+ fprintf(stderr, "Couldn't match a Visual with double b… | |
+ 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, … | |
+ state.w, state.h, 0, state.depth, | |
+ InputOutput, state.vis, CWBackPixmap | CWColormap, &attrs); | |
+ | |
+ state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, Xdbe… | |
+ | |
+ 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 … | |
+ | |
+ 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)) == NUL… | |
+ XSetLocaleModifiers("@im="); | |
+ if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL))… | |
+ fprintf(stderr, "XOpenIM failed. Could not ope… | |
+ exit(1); | |
+ } | |
+ } | |
+ } | |
+ state.xic = XCreateIC(state.xim, XNInputStyle, XIMPreeditNothing | |
+ | XIMStatusNothing, XNClientWindow,… | |
+ XNFocusWindow, state.win, NULL); | |
+ if (state.xic == NULL) { | |
+ fprintf(stderr, "XCreateIC failed. Could not obtain input meth… | |
+ 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, sta… | |
+} | |
+ | |
+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 -… | |
+ 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] & … | |
+ 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_in… | |
+ /* 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].te… | |
+ 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_i… | |
+ 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, &li… | |
+ int maxlines = pango_layout_get_line_count(lines[cur_line].layout); | |
+ PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line]… | |
+ 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_read… | |
+ if (pango_layout_line_x_to_index(nextline, x, &cur_ind… | |
+ /* set it to *after* the last index of the lin… | |
+ cur_index = nextline->start_index + nextline->… | |
+ } | |
+ } | |
+ } else { | |
+ /* move to the next soft line */ | |
+ PangoLayoutLine *nextline = pango_layout_get_line_readonly(lin… | |
+ if (pango_layout_line_x_to_index(nextline, x, &cur_index, &tra… | |
+ /* 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… | |
+ 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, &li… | |
+ PangoLayoutLine *line = pango_layout_get_line_readonly(lines[cur_line]… | |
+ 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_l… | |
+ PangoLayoutLine *prevline = pango_layout_get_line_read… | |
+ if (pango_layout_line_x_to_index(prevline, x, &cur_ind… | |
+ /* set it to *after* the last index of the lin… | |
+ cur_index = prevline->start_index + prevline->… | |
+ } | |
+ } | |
+ } else { | |
+ /* move to the previous soft line */ | |
+ PangoLayoutLine *prevline = pango_layout_get_line_readonly(lin… | |
+ if (pango_layout_line_x_to_index(prevline, x, &cur_index, &tra… | |
+ /* 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… | |
+ 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), &s… | |
+ 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) && !… | |
+ cur_keys->keys[i].func(); | |
+ found = 1; | |
+ } | |
+ } else if ((cur_keys->keys[i].modes & cur_mode) && cur_keys->k… | |
+ 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; | |
+ } | |
+} |