A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

208 lines
7.8 KiB

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Avalonia.FreeDesktop;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform.Interop;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
partial class X11Window
{
private ITextInputMethodImpl _ime;
private IX11InputMethodControl _imeControl;
private bool _processingIme;
private Queue<(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)> _imeQueue =
new Queue<(RawKeyEventArgs args, XEvent xev, int keyVal, int keyCode)>();
unsafe void CreateIC()
{
if (_x11.HasXim)
{
XGetIMValues(_x11.Xim, XNames.XNQueryInputStyle, out var supported_styles, IntPtr.Zero);
for (var c = 0; c < supported_styles->count_styles; c++)
{
var style = (XIMProperties)supported_styles->supported_styles[c];
if ((int)(style & XIMProperties.XIMPreeditPosition) != 0
&& ((int)(style & XIMProperties.XIMStatusNothing) != 0))
{
XPoint spot = default;
XRectangle area = default;
//using var areaS = new Utf8Buffer("area");
using var spotS = new Utf8Buffer("spotLocation");
using var fontS = new Utf8Buffer("fontSet");
var list = XVaCreateNestedList(0,
//areaS, &area,
spotS, &spot,
fontS, _x11.DefaultFontSet,
IntPtr.Zero);
_xic = XCreateIC(_x11.Xim,
XNames.XNClientWindow, _handle,
XNames.XNFocusWindow, _handle,
XNames.XNInputStyle, new IntPtr((int)style),
XNames.XNResourceName, _platform.Options.WmClass,
XNames.XNResourceClass, _platform.Options.WmClass,
XNames.XNPreeditAttributes, list,
IntPtr.Zero);
XFree(list);
break;
}
}
XFree(new IntPtr(supported_styles));
}
if (_xic == IntPtr.Zero)
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle,
new IntPtr((int)(XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing)),
XNames.XNClientWindow, _handle, XNames.XNFocusWindow, _handle, IntPtr.Zero);
}
void InitializeIme()
{
var ime = AvaloniaLocator.Current.GetService<IX11InputMethodFactory>()?.CreateClient(_handle);
if (ime == null && _x11.HasXim)
{
var xim = new XimInputMethod(this);
ime = (xim, xim);
}
if (ime != null)
{
(_ime, _imeControl) = ime.Value;
_imeControl.Commit += s =>
ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)_x11.LastActivityTimestamp.ToInt64(),
_inputRoot, s));
_imeControl.ForwardKey += ev =>
{
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)_x11.LastActivityTimestamp.ToInt64(),
_inputRoot, ev.Type, X11KeyTransform.ConvertKey((X11Key)ev.KeyVal),
(RawInputModifiers)ev.Modifiers));
};
}
}
void UpdateImePosition() => _imeControl?.UpdateWindowInfo(Position, RenderScaling);
void HandleKeyEvent(ref XEvent ev)
{
var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
var filtered = ScheduleKeyInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot,
ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev, (int)key, ev.KeyEvent.keycode);
if (ev.type == XEventName.KeyPress && !filtered)
TriggerClassicTextInputEvent(ref ev);
}
void TriggerClassicTextInputEvent(ref XEvent ev)
{
var text = TranslateEventToString(ref ev);
if (text != null)
ScheduleInput(
new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot, text),
ref ev);
}
private const int ImeBufferSize = 64 * 1024;
[ThreadStatic] private static IntPtr ImeBuffer;
unsafe string TranslateEventToString(ref XEvent ev)
{
if (ImeBuffer == IntPtr.Zero)
ImeBuffer = Marshal.AllocHGlobal(ImeBufferSize);
var len = Xutf8LookupString(_xic, ref ev, ImeBuffer.ToPointer(), ImeBufferSize,
out _, out var istatus);
var status = (XLookupStatus)istatus;
if (len == 0)
return null;
string text;
if (status == XLookupStatus.XBufferOverflow)
return null;
else
text = Encoding.UTF8.GetString((byte*)ImeBuffer.ToPointer(), len);
if (text == null)
return null;
if (text.Length == 1)
{
if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL
return null;
}
return text;
}
bool ScheduleKeyInput(RawKeyEventArgs args, ref XEvent xev, int keyval, int keycode)
{
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
if (_imeControl != null && _imeControl.IsEnabled)
{
if (FilterIme(args, xev, keyval, keycode))
return true;
}
ScheduleInput(args);
return false;
}
bool FilterIme(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)
{
if (_ime == null)
return false;
_imeQueue.Enqueue((args, xev, keyval, keycode));
if (!_processingIme)
ProcessNextImeEvent();
return true;
}
async void ProcessNextImeEvent()
{
if(_processingIme)
return;
_processingIme = true;
try
{
while (_imeQueue.Count != 0)
{
var ev = _imeQueue.Dequeue();
if (_imeControl == null || !await _imeControl.HandleEventAsync(ev.args, ev.keyval, ev.keycode))
{
ScheduleInput(ev.args);
if (ev.args.Type == RawKeyEventType.KeyDown)
TriggerClassicTextInputEvent(ref ev.xev);
}
}
}
finally
{
_processingIme = false;
}
}
}
}