csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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.
192 lines
7.5 KiB
192 lines
7.5 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;
|
|
private int _insideReset = 0;
|
|
|
|
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, (VariantValue Text, uint CursorPos, bool Visible) preeditComponents)
|
|
{
|
|
if (preeditComponents.Text is { Type: VariantValueType.Struct, Count: >= 3 } structItem && structItem.GetItem(2) is { Type: VariantValueType.String} stringItem)
|
|
{
|
|
_preeditText = stringItem.GetString();
|
|
_preeditCursor = _preeditText != null
|
|
? Utf16Utils.CharacterOffsetToStringOffset(_preeditText,
|
|
(int)Math.Min(preeditComponents.CursorPos, int.MaxValue), false)
|
|
: 0;
|
|
|
|
_preeditShown = true;
|
|
}
|
|
else
|
|
{
|
|
_preeditText = null;
|
|
_preeditShown = false;
|
|
_preeditCursor = 0;
|
|
}
|
|
|
|
if (Client?.SupportsPreedit == true)
|
|
Client.SetPreeditText(
|
|
_preeditShown ? _preeditText : null, _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, VariantValue variantItem)
|
|
{
|
|
if (_insideReset > 0)
|
|
{
|
|
// For some reason iBus can trigger a CommitText while being reset.
|
|
// Thankfully the signal is sent _during_ Reset call processing,
|
|
// so it arrives on-the-wire before Reset call result, so we can
|
|
// check if we have any pending Reset calls and ignore the signal here
|
|
return;
|
|
}
|
|
if (e is not null)
|
|
{
|
|
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnCommitText failed: {e}");
|
|
return;
|
|
}
|
|
|
|
if (variantItem.Count >= 3 && variantItem.GetItem(2) is { Type: VariantValueType.String } stringItem)
|
|
FireCommit(stringItem.GetString());
|
|
}
|
|
|
|
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 async Task ResetContextCore()
|
|
{
|
|
_preeditShown = true;
|
|
if (_context == null)
|
|
return;
|
|
if (_context == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
_insideReset++;
|
|
await _context.ResetAsync();
|
|
}
|
|
finally
|
|
{
|
|
_insideReset--;
|
|
}
|
|
}
|
|
|
|
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;
|
|
if (_context != null)
|
|
await _context.SetCapabilitiesAsync((uint)caps);
|
|
}
|
|
}
|
|
}
|
|
|