Hi guys,
I've lately solved couple of puzzles around XIM and I can enter
characters from XIM now.
I had couple of extra changes (from my experimental fixes), so I reduced
the changes and
now it is trimmed down to the attached patch, which should be without
any side effect.
Can someone review it or can I apply these changes? Those XIM support
work will go on
as there should be couple more of improvements, but incremental changes
would be
better (at least for me :).
After these changes, we'll still need:
- better Preedit/Status support (currently it has been
PreeditNothing/StatusNothing).
- some fixage for some IM engine for preedit window (I disabled one
XMapWindow() that
is however required for "atokx3" or iiimf).
- add some context menu items for international text input to TextBox
and RichTextBox.
I have some prepared answers for possible questions:
- I needed to change StringBuilder argument to byte[] in
Xutf8LookupString() because it returns
length in UTF-8 bytes and the string length cannot be retrieved with
StringBuilder.
- I had to move EnsureKeyboardInitialized() to very early stage i.e.
X11Keyboard.ctor()
because the late initialization somehow caused missing IM startup sometimes.
- XFilterEvent() is required for non-KeyPress/KeyRelease event for some
(or every?) IM engine(s)
especially as for ClientMessageEvent.
Atsushi Eno
Index: System.Windows.Forms/X11Keyboard.cs
===================================================================
--- System.Windows.Forms/X11Keyboard.cs (revision 98231)
+++ System.Windows.Forms/X11Keyboard.cs (working copy)
@@ -43,7 +43,9 @@
private IntPtr display;
private IntPtr window;
private IntPtr xic;
+ private EventMask xic_event_mask = EventMask.NoEventMask;
private StringBuilder lookup_buffer;
+ private byte [] utf8_buffer;
private int min_keycode, max_keycode, keysyms_per_keycode, syms;
private int [] keyc2vkey = new int [256];
private int [] keyc2scan = new int [256];
@@ -61,8 +63,18 @@
this.display = display;
this.window = window;
lookup_buffer = new StringBuilder (24);
+ EnsureLayoutInitialized ();
}
+ ~X11Keyboard ()
+ {
+ if (xic != IntPtr.Zero) {
+ IntPtr im = XIMOfIC (xic);
+ XDestroyIC (xic);
+ XCloseIM (im);
+ }
+ }
+
public void EnsureLayoutInitialized ()
{
if (initialized)
@@ -86,10 +98,27 @@
Console.Error.WriteLine ("Could not get XIM");
else
xic = CreateXic (window, xim);
-
+ if (xic != IntPtr.Zero)
+ utf8_buffer = new byte [100];
+ if (XGetICValues (xic, "filterEvents", out xic_event_mask, IntPtr.Zero) != null)
+ Console.Error.WriteLine ("Could not get XIC values");
+ EventMask mask = EventMask.ExposureMask | EventMask.KeyPressMask | EventMask.FocusChangeMask;
+ xic_event_mask |= mask;
+ XplatUIX11.XSelectInput(display, window, new IntPtr ((int) xic_event_mask));
+ // FIXME: without it some input methods do not show its UI (but it results in
+ // obstacle, so am disabling it).
+ // XplatUIX11.XMapWindow (display, window);
initialized = true;
}
+ internal IntPtr Window {
+ get { return window; }
+ }
+
+ public EventMask KeyEventMask {
+ get { return xic_event_mask; }
+ }
+
public Keys ModifierKeys {
get {
Keys keys = Keys.None;
@@ -134,8 +163,6 @@
public void KeyEvent (IntPtr hwnd, XEvent xevent, ref MSG msg)
{
- EnsureLayoutInitialized ();
-
XKeySym keysym;
int ascii_chars;
@@ -155,8 +182,9 @@
int event_time = (int)xevent.KeyEvent.time;
if (status == (IntPtr) 2) {
- // Copy chars into a globally accessible var, i don't think
- // this var is exposed anywhere though, so we can just ignore this
+ // do not ignore those inputs. They are mostly from XIM.
+ msg = SendImeComposition (lookup_buffer.ToString (0, lookup_buffer.Length));
+ msg.hwnd = hwnd;
return;
}
@@ -361,6 +389,22 @@
return res;
}
+ string stored_keyevent_string;
+
+ internal string GetCompositionString ()
+ {
+ return stored_keyevent_string;
+ }
+
+ private MSG SendImeComposition (string s)
+ {
+ MSG msg = new MSG ();
+ msg.message = Msg.WM_IME_COMPOSITION;
+ msg.refobject = s;
+ stored_keyevent_string = s;
+ return msg;
+ }
+
private MSG SendKeyboardInput (VirtualKeys vkey, int scan, KeybdEventFlags dw_flags, int time)
{
Msg message;
@@ -450,8 +494,6 @@
public int EventToVkey (XEvent e)
{
- EnsureLayoutInitialized ();
-
IntPtr status;
XKeySym ks;
@@ -702,14 +744,23 @@
int res;
status = IntPtr.Zero;
- lookup_buffer.Length = 0;
- if (xic != IntPtr.Zero)
- res = Xutf8LookupString (xic, ref xevent, lookup_buffer, len, out keysym_res, out status);
- else
+ if (xic != IntPtr.Zero) {
+ do {
+ res = Xutf8LookupString (xic, ref xevent, utf8_buffer, 100, out keysym_res, out status);
+ if ((int) status != -1) // XLookupBufferOverflow
+ break;
+ utf8_buffer = new byte [utf8_buffer.Length << 1];
+ } while (true);
+ lookup_buffer.Length = 0;
+ string s = Encoding.UTF8.GetString (utf8_buffer, 0, res);
+ lookup_buffer.Append (s);
+ keysym = (XKeySym) keysym_res.ToInt32 ();
+ return s.Length;
+ } else {
res = XLookupString (ref xevent, lookup_buffer, len, out keysym_res, IntPtr.Zero);
-
- keysym = (XKeySym) keysym_res.ToInt32 ();
- return res;
+ keysym = (XKeySym) keysym_res.ToInt32 ();
+ return res;
+ }
}
[DllImport ("libX11")]
@@ -719,6 +770,18 @@
private static extern IntPtr XCreateIC (IntPtr xim, string name, XIMProperties im_style, string name2, IntPtr value2, string name3, IntPtr value3, IntPtr terminator);
[DllImport ("libX11")]
+ private static extern IntPtr XIMOfIC (IntPtr xic);
+
+ [DllImport ("libX11")]
+ private static extern void XCloseIM (IntPtr xim);
+
+ [DllImport ("libX11")]
+ private static extern void XDestroyIC (IntPtr xic);
+
+ [DllImport ("libX11")]
+ private static extern string XGetICValues (IntPtr xic, string name, out EventMask value, IntPtr terminator);
+
+ [DllImport ("libX11")]
private static extern void XSetICFocus (IntPtr xic);
[DllImport ("libX11")]
@@ -733,29 +796,8 @@
[DllImport ("libX11")]
internal extern static int XLookupString(ref XEvent xevent, StringBuilder buffer, int num_bytes, out IntPtr keysym, IntPtr status);
[DllImport ("libX11")]
- internal extern static int Xutf8LookupString(IntPtr xic, ref XEvent xevent, StringBuilder buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
+ internal extern static int Xutf8LookupString(IntPtr xic, ref XEvent xevent, byte [] buffer, int num_bytes, out IntPtr keysym, out IntPtr status);
- internal static int Xutf8LookupString (IntPtr xic, ref XEvent xevent, StringBuilder buffer, int num_bytes, out XKeySym keysym, out IntPtr status) {
- IntPtr keysym_ret;
- int ret;
-
- ret = Xutf8LookupString (xic, ref xevent, buffer, num_bytes, out keysym_ret, out status);
-
- keysym = (XKeySym)keysym_ret.ToInt32();
-
- return ret;
- }
-
- internal static int XLookupString (ref XEvent xevent, StringBuilder buffer, int num_bytes, out XKeySym keysym, IntPtr status) {
- IntPtr keysym_ret;
- int ret;
-
- ret = XLookupString (ref xevent, buffer, num_bytes, out keysym_ret, status);
- keysym = (XKeySym)keysym_ret.ToInt32();
-
- return ret;
- }
-
[DllImport ("libX11")]
private static extern IntPtr XGetKeyboardMapping (IntPtr display, byte first_keycode, int keycode_count,
out int keysyms_per_keycode_return);
Index: System.Windows.Forms/XplatUIX11.cs
===================================================================
--- System.Windows.Forms/XplatUIX11.cs (revision 98231)
+++ System.Windows.Forms/XplatUIX11.cs (working copy)
@@ -509,7 +509,7 @@
SetupAtoms();
// Grab atom changes off the root window to catch certain WM events
- XSelectInput(DisplayHandle, RootWindow, new IntPtr ((int)EventMask.PropertyChangeMask));
+ XSelectInput(DisplayHandle, RootWindow, new IntPtr ((int) (EventMask.PropertyChangeMask | Keyboard.KeyEventMask)));
// Handle any upcoming errors
ErrorHandler = new XErrorHandler(HandleError);
@@ -1561,11 +1561,17 @@
XNextEvent (DisplayHandle, ref xevent);
- if (xevent.AnyEvent.type == XEventName.KeyPress) {
+ if (xevent.AnyEvent.type == XEventName.KeyPress ||
+ xevent.AnyEvent.type == XEventName.KeyRelease) {
if (XFilterEvent(ref xevent, FosterParent)) {
+ // probably here we could raise WM_IME_KEYDOWN and
+ // WM_IME_KEYUP, but I'm not sure it is worthy.
continue;
}
}
+
+ else if (XFilterEvent (ref xevent, IntPtr.Zero))
+ continue;
}
hwnd = Hwnd.GetObjectFromWindow(xevent.AnyEvent.window);
@@ -2672,9 +2678,9 @@
}
lock (XlibLock) {
- XSelectInput(DisplayHandle, hwnd.whole_window, new IntPtr ((int)(SelectInputMask | EventMask.StructureNotifyMask | EventMask.PropertyChangeMask)));
+ XSelectInput(DisplayHandle, hwnd.whole_window, new IntPtr ((int)(SelectInputMask | EventMask.StructureNotifyMask | EventMask.PropertyChangeMask | Keyboard.KeyEventMask)));
if (hwnd.whole_window != hwnd.client_window)
- XSelectInput(DisplayHandle, hwnd.client_window, new IntPtr ((int)(SelectInputMask | EventMask.StructureNotifyMask)));
+ XSelectInput(DisplayHandle, hwnd.client_window, new IntPtr ((int)(SelectInputMask | EventMask.StructureNotifyMask | Keyboard.KeyEventMask)));
}
if (ExStyleSet (cp.ExStyle, WindowExStyles.WS_EX_TOPMOST)) {
@@ -3059,6 +3065,21 @@
internal override IntPtr DefWndProc(ref Message msg) {
switch ((Msg)msg.Msg) {
+
+ case Msg.WM_IME_COMPOSITION:
+ string s = Keyboard.GetCompositionString ();
+ foreach (char c in s)
+ SendMessage (msg.HWnd, Msg.WM_IME_CHAR, (IntPtr) c, msg.LParam);
+ return IntPtr.Zero;
+
+ case Msg.WM_IME_CHAR:
+ int c = (int) msg.WParam;
+ // On Windows API it sends two WM_CHAR messages for each byte, but
+ // I wonder if it is worthy to emulate it (also no idea how to
+ // reconstruct those bytes into chars).
+ SendMessage (msg.HWnd, Msg.WM_CHAR, msg.WParam, msg.LParam);
+ return IntPtr.Zero;
+
case Msg.WM_PAINT: {
Hwnd hwnd;
@@ -5122,6 +5143,7 @@
prev_focus_window = FocusWindow;
FocusWindow = hwnd.client_window;
+ Keyboard.FocusIn (FocusWindow);
if (prev_focus_window != IntPtr.Zero) {
SendMessage(prev_focus_window, Msg.WM_KILLFOCUS, FocusWindow, IntPtr.Zero);
_______________________________________________
Mono-winforms-list maillist - [email protected]
http://lists.ximian.com/mailman/listinfo/mono-winforms-list