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.
 
 
 

165 lines
6.6 KiB

using System;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Logging;
using Avalonia.Media.TextFormatting.Unicode;
using Tmds.DBus.Protocol;
using Tmds.DBus.SourceGenerator;
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
internal class IBusX11TextInputMethod : DBusTextInputMethodBase
{
private OrgFreedesktopIBusService? _service;
private OrgFreedesktopIBusInputContext? _context;
private string? _preeditText;
private int _preeditCursor;
private bool _preeditShown = true;
public IBusX11TextInputMethod(Connection connection) : base(connection, "org.freedesktop.portal.IBus") { }
protected override async Task<bool> Connect(string name)
{
var portal = new OrgFreedesktopIBusPortal(Connection, name, "/org/freedesktop/IBus");
var path = await portal.CreateInputContextAsync(GetAppName());
_service = new OrgFreedesktopIBusService(Connection, name, path);
_context = new OrgFreedesktopIBusInputContext(Connection, name, path);
AddDisposable(await _context.WatchCommitTextAsync(OnCommitText));
AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey));
AddDisposable(await _context.WatchUpdatePreeditTextAsync(OnUpdatePreedit));
AddDisposable(await _context.WatchShowPreeditTextAsync(OnShowPreedit));
AddDisposable(await _context.WatchHidePreeditTextAsync(OnHidePreedit));
Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus));
return true;
}
private void OnHidePreedit(Exception? obj)
{
_preeditShown = false;
if (Client?.SupportsPreedit == true)
Client.SetPreeditText(null, null);
}
private void OnShowPreedit(Exception? obj)
{
_preeditShown = true;
if (Client?.SupportsPreedit == true)
Client.SetPreeditText(_preeditText, _preeditText == null ? null : _preeditCursor);
}
private void OnUpdatePreedit(Exception? arg1, (DBusVariantItem text, uint cursor_pos, bool visible) preeditComponents)
{
if (preeditComponents.text.Value is DBusStructItem { Count: >= 3 } structItem &&
structItem[2] is DBusStringItem stringItem)
{
_preeditText = stringItem.Value;
_preeditCursor = _preeditText != null
? Utf16Utils.CharacterOffsetToStringOffset(_preeditText,
(int)Math.Min(preeditComponents.cursor_pos, int.MaxValue), false)
: 0;
_preeditShown = true;
if (Client?.SupportsPreedit == true)
Client.SetPreeditText(
_preeditText, _preeditCursor);
}
}
private void OnForwardKey(Exception? e, (uint keyval, uint keycode, uint state) k)
{
if (e is not null)
{
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnForwardKey failed: {e}");
return;
}
var state = (IBusModifierMask)k.state;
KeyModifiers mods = default;
if (state.HasAllFlags(IBusModifierMask.ControlMask))
mods |= KeyModifiers.Control;
if (state.HasAllFlags(IBusModifierMask.Mod1Mask))
mods |= KeyModifiers.Alt;
if (state.HasAllFlags(IBusModifierMask.ShiftMask))
mods |= KeyModifiers.Shift;
if (state.HasAllFlags(IBusModifierMask.Mod4Mask))
mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey
{
KeyVal = (int)k.keyval,
Type = state.HasAllFlags(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown,
Modifiers = mods
});
}
private void OnCommitText(Exception? e, DBusVariantItem variantItem)
{
if (e is not null)
{
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnCommitText failed: {e}");
return;
}
if (variantItem.Value is DBusStructItem { Count: >= 3 } structItem && structItem[2] is DBusStringItem stringItem)
FireCommit(stringItem.Value);
}
protected override Task DisconnectAsync() => _service?.DestroyAsync() ?? Task.CompletedTask;
protected override void OnDisconnected()
{
_service = null;
_context = null;
base.OnDisconnected();
}
protected override Task SetCursorRectCore(PixelRect rect)
=> _context?.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height)
?? Task.CompletedTask;
protected override Task SetActiveCore(bool active)
=> (active ? _context?.FocusInAsync() : _context?.FocusOutAsync())
?? Task.CompletedTask;
protected override Task ResetContextCore()
{
_preeditShown = true;
return _context?.ResetAsync() ?? Task.CompletedTask;
}
protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{
IBusModifierMask state = default;
if (args.Modifiers.HasAllFlags(RawInputModifiers.Control))
state |= IBusModifierMask.ControlMask;
if (args.Modifiers.HasAllFlags(RawInputModifiers.Alt))
state |= IBusModifierMask.Mod1Mask;
if (args.Modifiers.HasAllFlags(RawInputModifiers.Shift))
state |= IBusModifierMask.ShiftMask;
if (args.Modifiers.HasAllFlags(RawInputModifiers.Meta))
state |= IBusModifierMask.Mod4Mask;
if (args.Type == RawKeyEventType.KeyUp)
state |= IBusModifierMask.ReleaseMask;
return _context is not null ? _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state) : Task.FromResult(false);
}
public override void SetOptions(TextInputOptions options)
{
// No-op, because ibus
}
protected override async Task SetCapabilitiesCore(bool supportsPreedit, bool supportsSurroundingText)
{
var caps = IBusCapability.CapFocus;
if (supportsPreedit)
caps |= IBusCapability.CapPreeditText;
await _context.SetCapabilitiesAsync((uint)caps);
}
}
}