Compare commits

...

8 Commits

Author SHA1 Message Date
mintycube
cde29093ca Merge remote-tracking branch 'upstream/master' 2024-07-22 14:23:25 +05:00
mintycube
42dd4a2c08 Add gitattributes
For avoiding merge conflicts in readme.md when syncing forks
2024-07-22 14:21:52 +05:00
bakkeby
ea263d0999 highlight: correct declared utf8len signature 2024-07-18 10:00:53 +02:00
bakkeby
3b547d5263 drw.c: use the same pattern as ellipsis_width to check for infinite recursion
ref.
https://git.suckless.org/dmenu/commit/475d8093cb8d29d5756937bfa9e0b3b9e415f632.html
2024-07-18 09:59:32 +02:00
bakkeby
b980b0a359 render invalid utf8 sequences as U+FFFD
previously drw_text would do the width calculations as if
invalid utf8 sequences were replaced with U+FFFD but would pass
the invalid utf8 sequence to xft to render where xft would just
cut it off at the first invalid byte.

this change makes invalid utf8 render as U+FFFD and avoids
sending invalid sequences to xft. the following can be used to
check the behavior before and after the patch:

	$ printf "0\xef1234567\ntest" | dmenu

Ref: https://lists.suckless.org/dev/2407/35646.html

Ref.
https://git.suckless.org/dmenu/commit/59936c7d972587a47d61161279bb8e8abc0b02f3.html
2024-07-18 09:36:48 +02:00
bakkeby
d66c96ba0b overhaul utf8decode()
this changes the utf8decode function to:

* report when an error occurs
* report how many bytes to advance on error

these will be useful in the next commit to render invalid utf8
sequences.

the new implementation is also shorter and more direct.

ref.
https://git.suckless.org/dmenu/commit/51e32d49b56c86cd288c64fccf6cd765547781b9.html
2024-07-18 09:25:15 +02:00
Stein Gunnar Bakkeby
34b991503c
Adding input method patch (#31)
Adding input method patch ref. PR #22
2024-07-18 09:16:00 +02:00
bakkeby
1c584542f4 caseinsensitive: avoid declaring cistrstr twice 2024-07-17 08:50:07 +02:00
9 changed files with 303 additions and 63 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
README.md merge=ours

46
dmenu.c
View File

@ -178,15 +178,6 @@ static Clr *scheme[SchemeLast];
#include "config.h"
#if CASEINSENSITIVE_PATCH
static char * cistrstr(const char *s, const char *sub);
static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp;
static char *(*fstrstr)(const char *, const char *) = cistrstr;
#else
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr;
#endif // CASEINSENSITIVE_PATCH
static unsigned int
textw_clamp(const char *str, unsigned int n)
{
@ -216,6 +207,14 @@ static void run(void);
static void setup(void);
static void usage(void);
#if CASEINSENSITIVE_PATCH
static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp;
static char *(*fstrstr)(const char *, const char *) = cistrstr;
#else
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr;
#endif // CASEINSENSITIVE_PATCH
#include "patch/include.c"
static void
@ -270,6 +269,9 @@ cleanup(void)
size_t i;
XUngrabKey(dpy, AnyKey, AnyModifier, root);
#if INPUTMETHOD_PATCH
XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
#endif // INPUTMETHOD_PATCH
for (i = 0; i < SchemeLast; i++)
free(scheme[i]);
for (i = 0; items && items[i].text; ++i)
@ -1506,7 +1508,6 @@ run(void)
#if PRESELECT_PATCH
int i;
#endif // PRESELECT_PATCH
while (!XNextEvent(dpy, &ev)) {
#if PRESELECT_PATCH
if (preselected) {
@ -1520,8 +1521,15 @@ run(void)
preselected = 0;
}
#endif // PRESELECT_PATCH
#if INPUTMETHOD_PATCH
if (XFilterEvent(&ev, None))
continue;
if (composing)
continue;
#else
if (XFilterEvent(&ev, win))
continue;
#endif // INPUTMETHOD_PATCH
switch(ev.type) {
#if MOUSE_SUPPORT_PATCH
case ButtonPress:
@ -1788,13 +1796,16 @@ setup(void)
(unsigned char *) &dock, 1);
#endif // WMTYPE_PATCH
/* input methods */
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
die("XOpenIM failed: could not open input device");
#if INPUTMETHOD_PATCH
init_input_method(xim);
#else
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL);
#endif // INPUTMETHOD_PATCH
#if MANAGED_PATCH
if (managed) {
@ -1816,8 +1827,13 @@ setup(void)
XSelectInput(dpy, dws[i], FocusChangeMask);
XFree(dws);
}
#if !INPUTMETHOD_PATCH
grabfocus();
#endif // INPUTMETHOD_PATCH
}
#if INPUTMETHOD_PATCH
grabfocus();
#endif // INPUTMETHOD_PATCH
drw_resize(drw, mw, mh);
drawmenu();
}
@ -1933,6 +1949,10 @@ main(int argc, char *argv[])
#if XRESOURCES_PATCH
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("warning: no locale support\n", stderr);
#if INPUTMETHOD_PATCH
if (!XSetLocaleModifiers(""))
fputs("warning: could not set locale modifiers", stderr);
#endif // INPUTMETHOD_PATCH
if (!(dpy = XOpenDisplay(NULL)))
die("cannot open display");
@ -2165,6 +2185,10 @@ main(int argc, char *argv[])
#else // !XRESOURCES_PATCH
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("warning: no locale support\n", stderr);
#if INPUTMETHOD_PATCH
if (!XSetLocaleModifiers(""))
fputs("warning: could not set locale modifiers", stderr);
#endif // INPUTMETHOD_PATCH
if (!(dpy = XOpenDisplay(NULL)))
die("cannot open display");
screen = DefaultScreen(dpy);

98
drw.c
View File

@ -11,63 +11,50 @@
#if !PANGO_PATCH || HIGHLIGHT_PATCH
#define UTF_INVALID 0xFFFD
#define UTF_SIZ 4
static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
static long
utf8decodebyte(const char c, size_t *i)
static int
utf8decode(const char *s_in, long *u, int *err)
{
for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
return (unsigned char)c & ~utfmask[*i];
return 0;
}
static size_t
utf8validate(long *u, size_t i)
{
if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID;
for (i = 1; *u > utfmax[i]; ++i)
;
return i;
}
static size_t
utf8decode(const char *c, long *u, size_t clen)
{
size_t i, j, len, type;
long udecoded;
static const unsigned char lens[] = {
/* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
/* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */
/* 110XX */ 2, 2, 2, 2,
/* 1110X */ 3, 3,
/* 11110 */ 4,
/* 11111 */ 0, /* invalid */
};
static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 };
static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 };
const unsigned char *s = (const unsigned char *)s_in;
int len = lens[*s >> 3];
*u = UTF_INVALID;
if (!clen)
return 0;
udecoded = utf8decodebyte(c[0], &len);
if (!BETWEEN(len, 1, UTF_SIZ))
*err = 1;
if (len == 0)
return 1;
for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
if (type)
return j;
}
if (j < len)
return 0;
*u = udecoded;
utf8validate(u, len);
long cp = s[0] & leading_mask[len - 1];
for (int i = 1; i < len; ++i) {
if (s[i] == '\0' || (s[i] & 0xC0) != 0x80)
return i;
cp = (cp << 6) | (s[i] & 0x3F);
}
/* out of range, surrogate, overlong encoding */
if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1])
return len;
*err = 0;
*u = cp;
return len;
}
#if HIGHLIGHT_PATCH
size_t
int
utf8len(const char *c)
{
long utf8codepoint = 0;
return utf8decode(c, &utf8codepoint, UTF_SIZ);
int utf8err = 0;
return utf8decode(c, &utf8codepoint, &utf8err);
}
#endif // HIGHLIGHT_PATCH
#endif // PANGO_PATCH
@ -447,7 +434,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1;
XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont;
int utf8strlen, utf8charlen, render = x || y || w || h;
int utf8strlen, utf8charlen, utf8err, render = x || y || w || h;
long utf8codepoint = 0;
const char *utf8str;
FcCharSet *fccharset;
@ -456,7 +443,8 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
XftResult result;
int charexists = 0, overflow = 0;
/* keep track of a couple codepoints for which we have no match. */
static unsigned int nomatches[128], ellipsis_width;
static unsigned int nomatches[128], ellipsis_width, invalid_width;
static const char invalid[] = "<EFBFBD>";
const char *ellipsis = "...";
if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
@ -481,12 +469,14 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
usedfont = drw->fonts;
if (!ellipsis_width && render)
ellipsis_width = drw_fontset_getwidth(drw, ellipsis);
if (!invalid_width && render)
invalid_width = drw_fontset_getwidth(drw, invalid);
while (1) {
ew = ellipsis_len = utf8strlen = 0;
ew = ellipsis_len = utf8err = utf8strlen = 0;
utf8str = text;
nextfont = NULL;
while (*text) {
utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
utf8charlen = utf8decode(text, &utf8codepoint, &utf8err);
for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) {
@ -508,9 +498,9 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
else
utf8strlen = ellipsis_len;
} else if (curfont == usedfont) {
utf8strlen += utf8charlen;
text += utf8charlen;
ew += tmpw;
utf8strlen += utf8err ? 0 : utf8charlen;
ew += utf8err ? 0 : tmpw;
} else {
nextfont = curfont;
}
@ -518,7 +508,7 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
}
}
if (overflow || !charexists || nextfont)
if (overflow || !charexists || nextfont || utf8err)
break;
else
charexists = 0;
@ -533,6 +523,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
x += ew;
w -= ew;
}
if (utf8err && (!render || invalid_width < w)) {
if (render)
drw_text(drw, x, y, w, h, 0, invalid, invert);
x += invalid_width;
w -= invalid_width;
}
if (render && overflow && ellipsis_w)
drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, ellipsis, invert);

2
drw.h
View File

@ -69,7 +69,7 @@ void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned in
#endif // PANGO_PATCH
#if HIGHLIGHT_PATCH
size_t utf8len(const char *c);
int utf8len(const char *c);
#endif // HIGHLIGHT_PATCH
/* Colorscheme abstraction */

View File

@ -13,6 +13,9 @@
#if HIGHPRIORITY_PATCH
#include "highpriority.c"
#endif
#if INPUTMETHOD_PATCH
#include "inputmethod.c"
#endif
#if DYNAMIC_OPTIONS_PATCH
#include "dynamicoptions.c"
#endif

View File

@ -4,6 +4,9 @@
#if FZFEXPECT_PATCH
#include "fzfexpect.h"
#endif
#if INPUTMETHOD_PATCH
#include "inputmethod.h"
#endif
#if MULTI_SELECTION_PATCH
#include "multiselect.h"
#endif

196
patch/inputmethod.c Normal file
View File

@ -0,0 +1,196 @@
static size_t nextrunetext(const char *text, size_t position, int inc)
{
ssize_t n;
/* return location of next utf8 rune in the given direction (+1 or -1) */
for (n = position + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
;
return n;
}
/* return bytes from beginning of text to nth utf8 rune to the right */
static size_t runebytes(const char *text, size_t n)
{
size_t ret;
ret = 0;
while (n-- > 0)
ret += nextrunetext(text + ret, 0, 1);
return ret;
}
/* return number of characters from beginning of text to nth byte to the right
*/
static size_t runechars(const char *text, size_t n)
{
size_t ret, i;
ret = i = 0;
while (i < n) {
i += nextrunetext(text + i, 0, 1);
ret++;
}
return ret;
}
/* move caret on pre-edit text */
static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata)
{
XIMPreeditCaretCallbackStruct *pcaret;
(void)xic;
pcaret = (XIMPreeditCaretCallbackStruct *)calldata;
if (!pcaret)
return;
switch (pcaret->direction) {
case XIMForwardChar:
cursor = nextrune(+1);
break;
case XIMBackwardChar:
cursor = nextrune(-1);
break;
case XIMForwardWord:
movewordedge(+1);
break;
case XIMBackwardWord:
movewordedge(-1);
break;
case XIMLineStart:
cursor = 0;
break;
case XIMLineEnd:
if (preview[cursor] != '\0')
cursor = strlen(preview);
break;
case XIMAbsolutePosition:
cursor = runebytes(text, pcaret->position);
break;
case XIMDontChange:
/* do nothing */
break;
case XIMCaretUp:
case XIMCaretDown:
case XIMNextLine:
case XIMPreviousLine:
/* not implemented */
break;
}
pcaret->position = runechars(preview, cursor);
}
/* start input method pre-editing */
static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata)
{
(void)xic;
(void)calldata;
(void)clientdata;
composing = 1;
printf("PREEDIT\n");
return BUFSIZ;
}
/* end input method pre-editing */
static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata)
{
(void)xic;
(void)clientdata;
(void)calldata;
printf("DONE\n");
composing = 0;
}
/* draw input method pre-edit text */
static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata)
{
XIMPreeditDrawCallbackStruct *pdraw;
size_t beg, dellen, inslen, endlen;
printf("DRAW\n");
(void)xic;
pdraw = (XIMPreeditDrawCallbackStruct *)calldata;
if (!pdraw)
return;
/* we do not support wide characters */
if (pdraw->text && pdraw->text->encoding_is_wchar == True) {
fputs("warning: wchar is not supportecd; use utf8", stderr);
return;
}
beg = runebytes(text, pdraw->chg_first);
dellen = runebytes(preview + beg, pdraw->chg_length);
inslen = pdraw->text ? runebytes(pdraw->text->string.multi_byte, pdraw->text->length) : 0;
endlen = 0;
if (beg + dellen < strlen(preview))
endlen = strlen(preview + beg + dellen);
/* we cannot change text past the end of our pre-edit string */
if (beg + dellen >= BUFSIZ || beg + inslen >= BUFSIZ)
return;
/* get space for text to be copied, and copy it */
memmove(preview + beg + inslen, preview + beg + dellen, endlen + 1);
if (pdraw->text && pdraw->text->length)
memcpy(preview + beg, pdraw->text->string.multi_byte, inslen);
(preview + beg + inslen + endlen)[0] = '\0';
/* get caret position */
cursor = runebytes(text, pdraw->caret);
}
static void init_input_method(XIM xim)
{
XVaNestedList preedit = NULL;
XICCallback start, done, draw, caret;
XIMStyle preeditstyle;
XIMStyle statusstyle;
XIMStyles *imstyles;
int i;
/* get styles supported by input method */
if (XGetIMValues(xim, XNQueryInputStyle, &imstyles, NULL) != NULL)
fputs("XGetIMValues: could not obtain input method values", stderr);
/* check whether input method support on-the-spot pre-editing */
preeditstyle = XIMPreeditNothing;
statusstyle = XIMStatusNothing;
for (i = 0; i < imstyles->count_styles; i++) {
if (imstyles->supported_styles[i] & XIMPreeditCallbacks) {
preeditstyle = XIMPreeditCallbacks;
break;
}
}
/* create callbacks for the input context */
start.client_data = NULL;
done.client_data = NULL;
draw.client_data = (XPointer)text;
caret.client_data = (XPointer)text;
start.callback = (XICProc)preeditstart;
done.callback = (XICProc)preeditdone;
draw.callback = (XICProc)preeditdraw;
caret.callback = (XICProc)preeditcaret;
/* create list of values for input context */
preedit = XVaCreateNestedList(0, XNPreeditStartCallback, &start, XNPreeditDoneCallback,
&done, XNPreeditDrawCallback, &draw, XNPreeditCaretCallback,
&caret, NULL);
if (preedit == NULL)
fputs("XVaCreateNestedList: could not create nested list", stderr);
xic = XCreateIC(xim, XNInputStyle, preeditstyle | statusstyle, XNPreeditAttributes, preedit,
XNClientWindow, win, XNFocusWindow, win, NULL);
XFree(preedit);
long eventmask;
/* get events the input method is interested in */
if (XGetICValues(xic, XNFilterEvents, &eventmask, NULL))
fputs("XGetICValues: could not obtain input context values", stderr);
XSelectInput(dpy, win,
ExposureMask | KeyPressMask | VisibilityChangeMask | ButtonPressMask |
PointerMotionMask | eventmask);
}

11
patch/inputmethod.h Normal file
View File

@ -0,0 +1,11 @@
static int composing;
static char preview[512] = "";
static size_t nextrunetext(const char *text, size_t position, int inc);
static size_t runebytes(const char *text, size_t n);
static size_t runechars(const char *text, size_t n);
static void preeditcaret(XIC xic, XPointer clientdata, XPointer calldata);
static int preeditstart(XIC xic, XPointer clientdata, XPointer calldata);
static void preeditdone(XIC xic, XPointer clientdata, XPointer calldata);
static void preeditdraw(XIC xic, XPointer clientdata, XPointer calldata);
static void init_input_method(XIM xim);

View File

@ -108,6 +108,12 @@
*/
#define INITIALTEXT_PATCH 0
/* Adds support for input methods (fctix, ibus, etc.) allowing the user to change the
* keyboard layout while dmenu is open.
* https://github.com/bakkeby/dmenu-flexipatch/pull/22
*/
#define INPUTMETHOD_PATCH 0
/* This patch adds a flag which will cause dmenu to select an item immediately if there
* is only one matching option left.
* https://tools.suckless.org/dmenu/patches/instant/