XIM for i18n input, by ketmar

This commit is contained in:
Adam D. Ruppe 2015-12-03 10:57:05 -05:00
parent b5ef7e41b8
commit 646578792f
1 changed files with 111 additions and 97 deletions

View File

@ -4220,6 +4220,28 @@ version(X11) {
/// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display* /// Platform-specific for X11. A singleton class (well, all its methods are actually static... so more like a namespace) wrapping a Display*
class XDisplayConnection { class XDisplayConnection {
private static Display* display; private static Display* display;
private static XIM xim;
// Do you want to know why do we need all this horrible-looking code? See comment at the bottom.
private static void createXIM () {
import core.stdc.locale : setlocale, LC_ALL;
import core.stdc.stdio : stderr, fprintf;
import core.stdc.stdlib : free;
import core.stdc.string : strdup;
static immutable string[] mtry = [ null, "@im=local", "@im=" ];
auto olocale = strdup(setlocale(LC_ALL, null));
setlocale(LC_ALL, (sdx_isUTF8Locale ? "" : "en_US.UTF-8"));
scope(exit) { setlocale(LC_ALL, olocale); free(olocale); }
//fprintf(stderr, "opening IM...\n");
foreach (string s; mtry) {
if (s.length) XSetLocaleModifiers(s.ptr); // it's safe, as `s` is string literal
if ((xim = XOpenIM(display, null, null, null)) !is null) return;
}
fprintf(stderr, "createXIM: XOpenIM failed!\n");
}
/// ///
static Display* get() { static Display* get() {
@ -4229,6 +4251,7 @@ version(X11) {
throw new Exception("Unable to open X display"); throw new Exception("Unable to open X display");
Bool sup; Bool sup;
XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released XkbSetDetectableAutoRepeat(display, 1, &sup); // so we will not receive KeyRelease until key is really released
createXIM();
version(with_eventloop) { version(with_eventloop) {
import arsd.eventloop; import arsd.eventloop;
addFileEventListeners(display.fd, &eventListener, null, null); addFileEventListeners(display.fd, &eventListener, null, null);
@ -4390,6 +4413,7 @@ version(X11) {
Display* display; Display* display;
Pixmap buffer; Pixmap buffer;
XIC xic; // input context
void delegate(XEvent) setSelectionHandler; void delegate(XEvent) setSelectionHandler;
void delegate(in char[]) getSelectionHandler; void delegate(in char[]) getSelectionHandler;
@ -4456,6 +4480,20 @@ version(X11) {
XSetForeground(display, gc, BlackPixel(display, screen)); XSetForeground(display, gc, BlackPixel(display, screen));
} }
// input context
//TODO: create this only for top-level windows, and reuse that?
if (XDisplayConnection.xim !is null) {
xic = XCreateIC(XDisplayConnection.xim,
/*XNInputStyle*/"inputStyle".ptr, XIMPreeditNothing|XIMStatusNothing,
/*XNClientWindow*/"clientWindow".ptr, window,
/*XNFocusWindow*/"focusWindow".ptr, window,
null);
if (xic is null) {
import core.stdc.stdio : stderr, fprintf;
fprintf(stderr, "XCreateIC failed for window %u\n", cast(uint)window);
}
}
setTitle(title); setTitle(title);
SimpleWindow.nativeMapping[window] = this; SimpleWindow.nativeMapping[window] = this;
CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this; CapableOfHandlingNativeEvent.nativeHandleMapping[window] = this;
@ -4777,6 +4815,18 @@ version(X11) {
XEvent e; XEvent e;
XNextEvent(display, &e); XNextEvent(display, &e);
version(sdddd) { import std.stdio, std.conv : to; writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); } version(sdddd) { import std.stdio, std.conv : to; writeln("event for: ", e.xany.window, "; type is ", to!string(cast(EventType)e.type)); }
// filter out compose events
if (XFilterEvent(&e, None)) {
//{ import core.stdc.stdio : printf; printf("XFilterEvent filtered!\n"); }
//NOTE: we should ungrab keyboard here, but simpledisplay doesn't use keyboard grabbing (yet)
return false;
}
// process keyboard mapping changes
if (e.type == EventType.KeymapNotify) {
//{ import core.stdc.stdio : printf; printf("KeymapNotify processed!\n"); }
XRefreshKeyboardMapping(&e.xmapping);
return false;
}
version(with_eventloop) version(with_eventloop)
import arsd.eventloop; import arsd.eventloop;
@ -4895,6 +4945,10 @@ version(X11) {
case EventType.FocusIn: case EventType.FocusIn:
case EventType.FocusOut: case EventType.FocusOut:
if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) { if(auto win = e.xfocus.window in SimpleWindow.nativeMapping) {
if (win.xic !is null) {
//{ import core.stdc.stdio : printf; printf("XIC focus change!\n"); }
if (e.type == EventType.FocusIn) XSetICFocus(win.xic); else XUnsetICFocus(win.xic);
}
if(win.onFocusChange) if(win.onFocusChange)
win.onFocusChange(e.type == EventType.FocusIn); win.onFocusChange(e.type == EventType.FocusIn);
} }
@ -4920,14 +4974,18 @@ version(X11) {
break; break;
case EventType.UnmapNotify: case EventType.UnmapNotify:
if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) { if(auto win = e.xunmap.window in SimpleWindow.nativeMapping) {
(*win)._visible = false; win._visible = false;
if ((*win).visibilityChanged !is null) (*win).visibilityChanged(false); if (win.visibilityChanged !is null) win.visibilityChanged(false);
} }
break; break;
case EventType.DestroyNotify: case EventType.DestroyNotify:
if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) { if(auto win = e.xdestroywindow.window in SimpleWindow.nativeMapping) {
(*win)._closed = true; // just in case win._closed = true; // just in case
(*win).destroyed = true; win.destroyed = true;
if (win.xic !is null) {
XDestroyIC(win.xic);
win.xic = null; // just in calse
}
SimpleWindow.nativeMapping.remove(e.xdestroywindow.window); SimpleWindow.nativeMapping.remove(e.xdestroywindow.window);
if(SimpleWindow.nativeMapping.keys.length == 0) if(SimpleWindow.nativeMapping.keys.length == 0)
done = true; done = true;
@ -4995,6 +5053,7 @@ version(X11) {
case EventType.KeyPress: case EventType.KeyPress:
case EventType.KeyRelease: case EventType.KeyRelease:
//if (e.type == EventType.KeyPress) { import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "X11 keyboard event!\n"); }
KeyEvent ke; KeyEvent ke;
ke.pressed = e.type == EventType.KeyPress; ke.pressed = e.type == EventType.KeyPress;
ke.hardwareCode = e.xkey.keycode; ke.hardwareCode = e.xkey.keycode;
@ -5008,35 +5067,45 @@ version(X11) {
ke.modifierState = e.xkey.state; ke.modifierState = e.xkey.state;
// Xutf8LookupString
// import std.stdio; writefln("%x", sym); // import std.stdio; writefln("%x", sym);
if(sym != 0 && ke.pressed) { wchar_t[128] charbuf = void; // buffer for XwcLookupString; composed value can consist of many chars!
char[16] buffer; int charbuflen = 0; // return value of XwcLookupString
auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null); if (ke.pressed) {
if(res && buffer[0] < 128) auto win = e.xkey.window in SimpleWindow.nativeMapping;
ke.character = cast(dchar) buffer[0]; if (win !is null && win.xic !is null) {
//{ import core.stdc.stdio : printf; printf("using xic!\n"); }
Status status;
charbuflen = XwcLookupString(win.xic, &e.xkey, charbuf.ptr, cast(int)charbuf.length, &sym, &status);
//{ import core.stdc.stdio : printf; printf("charbuflen=%d\n", charbuflen); }
} else {
//{ import core.stdc.stdio : printf; printf("NOT using xic!\n"); }
// If XIM initialization failed, don't process intl chars. Sorry, boys and girls.
char[16] buffer;
auto res = XLookupString(&e.xkey, buffer.ptr, buffer.length, null, null);
if (res && buffer[0] < 128) charbuf[charbuflen++] = cast(wchar_t)buffer[0];
}
} }
switch(sym) { // if there's no char, subst one
case 0xff09: ke.character = '\t'; break; if (charbuflen == 0) {
case 0xff8d: // keypad enter switch (sym) {
case 0xff0d: ke.character = '\n'; break; case 0xff09: charbuf[charbuflen++] = '\t'; break;
default : // ignore case 0xff8d: // keypad enter
case 0xff0d: charbuf[charbuflen++] = '\n'; break;
default : // ignore
}
} }
if(auto win = e.xkey.window in SimpleWindow.nativeMapping) { if (auto win = e.xkey.window in SimpleWindow.nativeMapping) {
ke.window = *win; ke.window = *win;
if (win.handleKeyEvent) win.handleKeyEvent(ke);
if((*win).handleKeyEvent)
(*win).handleKeyEvent(ke);
// char events are separate since they are on Windows too // char events are separate since they are on Windows too
if(ke.pressed && ke.character != dchar.init) { // also, xcompose can generate long char sequences
if (ke.pressed && charbuflen > 0) {
// FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats. // FIXME: I think Windows sends these on releases... we should try to match that, but idk about repeats.
if((*win).handleCharEvent) { foreach (immutable dchar ch; charbuf[0..charbuflen]) {
(*win).handleCharEvent(ke.character); if (win.handleCharEvent) win.handleCharEvent(ch);
} }
} }
} }
@ -5381,6 +5450,7 @@ else version(X11) {
pragma(lib, "X11"); pragma(lib, "X11");
pragma(lib, "Xext"); pragma(lib, "Xext");
import core.stdc.stddef : wchar_t;
extern(C): extern(C):
@ -5390,6 +5460,8 @@ int XUndefineCursor(Display* display, Window w);
int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out); int XLookupString(XKeyEvent *event_struct, char *buffer_return, int bytes_buffer, KeySym *keysym_return, void *status_in_out);
int XwcLookupString(XIC ic, XKeyPressedEvent* event, wchar_t* buffer_return, int wchars_buffer, KeySym* keysym_return, Status* status_return);
char *XKeysymToString(KeySym keysym); char *XKeysymToString(KeySym keysym);
KeySym XKeycodeToKeysym( KeySym XKeycodeToKeysym(
Display* /* display */, Display* /* display */,
@ -5471,50 +5543,6 @@ enum : arch_ulong {
XIMStatusNone = 0x0800, XIMStatusNone = 0x0800,
} }
immutable char* XNVaNestedList = "XNVaNestedList";
immutable char* XNQueryInputStyle = "queryInputStyle";
immutable char* XNClientWindow = "clientWindow";
immutable char* XNInputStyle = "inputStyle";
immutable char* XNFocusWindow = "focusWindow";
immutable char* XNResourceName = "resourceName";
immutable char* XNResourceClass = "resourceClass";
immutable char* XNGeometryCallback = "geometryCallback";
immutable char* XNDestroyCallback = "destroyCallback";
immutable char* XNFilterEvents = "filterEvents";
immutable char* XNPreeditStartCallback = "preeditStartCallback";
immutable char* XNPreeditDoneCallback = "preeditDoneCallback";
immutable char* XNPreeditDrawCallback = "preeditDrawCallback";
immutable char* XNPreeditCaretCallback = "preeditCaretCallback";
immutable char* XNPreeditStateNotifyCallback = "preeditStateNotifyCallback";
immutable char* XNPreeditAttributes = "preeditAttributes";
immutable char* XNStatusStartCallback = "statusStartCallback";
immutable char* XNStatusDoneCallback = "statusDoneCallback";
immutable char* XNStatusDrawCallback = "statusDrawCallback";
immutable char* XNStatusAttributes = "statusAttributes";
immutable char* XNArea = "area";
immutable char* XNAreaNeeded = "areaNeeded";
immutable char* XNSpotLocation = "spotLocation";
immutable char* XNColormap = "colorMap";
immutable char* XNStdColormap = "stdColorMap";
immutable char* XNForeground = "foreground";
immutable char* XNBackground = "background";
immutable char* XNBackgroundPixmap = "backgroundPixmap";
immutable char* XNFontSet = "fontSet";
immutable char* XNLineSpace = "lineSpace";
immutable char* XNCursor = "cursor";
immutable char* XNQueryIMValuesList = "queryIMValuesList";
immutable char* XNQueryICValuesList = "queryICValuesList";
immutable char* XNVisiblePosition = "visiblePosition";
immutable char* XNR6PreeditCallback = "r6PreeditCallback";
immutable char* XNStringConversionCallback = "stringConversionCallback";
immutable char* XNStringConversion = "stringConversion";
immutable char* XNResetState = "resetState";
immutable char* XNHotKey = "hotKey";
immutable char* XNHotKeyState = "hotKeyState";
immutable char* XNPreeditState = "preeditState";
immutable char* XNSeparatorofNestedList = "separatorofNestedList";
/* X Shared Memory Extension functions */ /* X Shared Memory Extension functions */
//pragma(lib, "Xshm"); //pragma(lib, "Xshm");
@ -6115,6 +6143,9 @@ int XNextEvent(
XEvent* /* event_return */ XEvent* /* event_return */
); );
Bool XFilterEvent(XEvent *event, Window window);
int XRefreshKeyboardMapping(XMappingEvent *event_map);
Status XSetWMProtocols( Status XSetWMProtocols(
Display* /* display */, Display* /* display */,
Window /* w */, Window /* w */,
@ -7973,17 +8004,16 @@ version(linux) {
version(X11) { version(X11) {
__gshared bool sdx_isUTF8Locale; __gshared bool sdx_isUTF8Locale;
version(linux) {
pragma(mangle, "strcasestr")
private extern(C) const(char)* strcasestr(const(char)* ns, const(char)* nd) nothrow @nogc;
}
// This whole crap is used to initialize X11 locale, so that you can use XIM methods later. // This whole crap is used to initialize X11 locale, so that you can use XIM methods later.
// Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will // Yes, there are people with non-utf locale (it's me, Ketmar!), but XIM (composing) will
// not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection" // not work right if app/X11 locale is not utf. This sux. That's why all that "utf detection"
// anal magic is here. I (Ketmar) hope you like it. // anal magic is here. I (Ketmar) hope you like it.
// We will use `sdx_isUTF8Locale` on XIM creation to enforce UTF-8 locale, so XCompose will
// always return correct unicode symbols. The detection is here 'cause user can change locale
// later.
shared static this () { shared static this () {
import core.stdc.locale; import core.stdc.locale : setlocale, LC_ALL, LC_CTYPE;
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
// check if out locale is UTF-8 // check if out locale is UTF-8
@ -7991,32 +8021,16 @@ shared static this () {
if (lct is null) { if (lct is null) {
sdx_isUTF8Locale = false; sdx_isUTF8Locale = false;
} else { } else {
version(linux) { for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) {
sdx_isUTF8Locale = (strcasestr(lct, "utf-8") !is null) || (strcasestr(lct, "utf8") !is null); if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') &&
} else { (lct[idx+1] == 't' || lct[idx+1] == 'T') &&
for (size_t idx = 0; lct[idx] && lct[idx+1] && lct[idx+2]; ++idx) { (lct[idx+2] == 'f' || lct[idx+2] == 'F'))
if ((lct[idx+0] == 'u' || lct[idx+0] == 'U') && {
(lct[idx+1] == 't' || lct[idx+1] == 'T') && sdx_isUTF8Locale = true;
(lct[idx+2] == 'f' || lct[idx+2] == 'F')) break;
{
sdx_isUTF8Locale = true;
break;
}
} }
} }
} }
//{ import core.stdc.stdio : stderr, fprintf; fprintf(stderr, "UTF8: %s\n", sdx_isUTF8Locale ? "tan".ptr : "ona".ptr); }
import core.stdc.stdlib : free;
import core.stdc.string : strdup;
auto olocale = strdup(setlocale(LC_ALL, null));
if (sdx_isUTF8Locale) {
if (!setlocale(LC_ALL, "")) assert(0, "simpledisplay: can't set locale");
} else {
if (!setlocale(LC_ALL, "en_US.UTF-8")) assert(0, "simpledisplay: can't set UTF locale");
}
// this will setup XIM, so we can use it later
if (XSupportsLocale()) XSetLocaleModifiers("@im=local");
setlocale(LC_ALL, olocale);
free(olocale);
} }
} }