7 changed files with 257 additions and 36 deletions
@ -0,0 +1,52 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] |
|||
namespace Avalonia.FreeDesktop.DBusIme.IBus |
|||
{ |
|||
[DBusInterface("org.freedesktop.IBus.InputContext")] |
|||
interface IIBusInputContext : IDBusObject |
|||
{ |
|||
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State); |
|||
Task SetCursorLocationAsync(int X, int Y, int W, int H); |
|||
Task FocusInAsync(); |
|||
Task FocusOutAsync(); |
|||
Task ResetAsync(); |
|||
Task SetCapabilitiesAsync(uint Caps); |
|||
Task PropertyActivateAsync(string Name, int State); |
|||
Task SetEngineAsync(string Name); |
|||
Task<object> GetEngineAsync(); |
|||
Task DestroyAsync(); |
|||
Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos); |
|||
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception> onError = null); |
|||
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception> onError = null); |
|||
} |
|||
|
|||
|
|||
[DBusInterface("org.freedesktop.IBus.Portal")] |
|||
interface IIBusPortal : IDBusObject |
|||
{ |
|||
Task<ObjectPath> CreateInputContextAsync(string Name); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.FreeDesktop.DBusIme.IBus |
|||
{ |
|||
[Flags] |
|||
internal enum IBusModifierMask |
|||
{ |
|||
ShiftMask = 1 << 0, |
|||
LockMask = 1 << 1, |
|||
ControlMask = 1 << 2, |
|||
Mod1Mask = 1 << 3, |
|||
Mod2Mask = 1 << 4, |
|||
Mod3Mask = 1 << 5, |
|||
Mod4Mask = 1 << 6, |
|||
Mod5Mask = 1 << 7, |
|||
Button1Mask = 1 << 8, |
|||
Button2Mask = 1 << 9, |
|||
Button3Mask = 1 << 10, |
|||
Button4Mask = 1 << 11, |
|||
Button5Mask = 1 << 12, |
|||
|
|||
HandledMask = 1 << 24, |
|||
ForwardMask = 1 << 25, |
|||
IgnoredMask = ForwardMask, |
|||
|
|||
SuperMask = 1 << 26, |
|||
HyperMask = 1 << 27, |
|||
MetaMask = 1 << 28, |
|||
|
|||
ReleaseMask = 1 << 30, |
|||
|
|||
ModifierMask = 0x5c001fff |
|||
} |
|||
|
|||
[Flags] |
|||
internal enum IBusCapability |
|||
{ |
|||
CapPreeditText = 1 << 0, |
|||
CapAuxiliaryText = 1 << 1, |
|||
CapLookupTable = 1 << 2, |
|||
CapFocus = 1 << 3, |
|||
CapProperty = 1 << 4, |
|||
CapSurroundingText = 1 << 5, |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Input.TextInput; |
|||
using Tmds.DBus; |
|||
|
|||
namespace Avalonia.FreeDesktop.DBusIme.IBus |
|||
{ |
|||
internal class IBusX11TextInputMethod : DBusTextInputMethodBase |
|||
{ |
|||
private IIBusInputContext _context; |
|||
|
|||
public IBusX11TextInputMethod(Connection connection) : base(connection, |
|||
"org.freedesktop.portal.IBus") |
|||
{ |
|||
} |
|||
|
|||
protected override async Task<bool> Connect(string name) |
|||
{ |
|||
var path = |
|||
await Connection.CreateProxy<IIBusPortal>(name, "/org/freedesktop/IBus") |
|||
.CreateInputContextAsync(GetAppName()); |
|||
|
|||
_context = Connection.CreateProxy<IIBusInputContext>(name, path); |
|||
AddDisposable(await _context.WatchCommitTextAsync(OnCommitText)); |
|||
AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey)); |
|||
Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus)); |
|||
return true; |
|||
} |
|||
|
|||
private void OnForwardKey((uint keyval, uint keycode, uint state) k) |
|||
{ |
|||
var state = (IBusModifierMask)k.state; |
|||
KeyModifiers mods = default; |
|||
if (state.HasFlagCustom(IBusModifierMask.ControlMask)) |
|||
mods |= KeyModifiers.Control; |
|||
if (state.HasFlagCustom(IBusModifierMask.Mod1Mask)) |
|||
mods |= KeyModifiers.Alt; |
|||
if (state.HasFlagCustom(IBusModifierMask.ShiftMask)) |
|||
mods |= KeyModifiers.Shift; |
|||
if (state.HasFlagCustom(IBusModifierMask.Mod4Mask)) |
|||
mods |= KeyModifiers.Meta; |
|||
FireForward(new X11InputMethodForwardedKey |
|||
{ |
|||
KeyVal = (int)k.keyval, |
|||
Type = state.HasFlagCustom(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown, |
|||
Modifiers = mods |
|||
}); |
|||
} |
|||
|
|||
|
|||
private void OnCommitText(object wtf) |
|||
{ |
|||
// Hello darkness, my old friend
|
|||
var prop = wtf.GetType().GetField("Item3"); |
|||
if (prop != null) |
|||
{ |
|||
var text = (string)prop.GetValue(wtf); |
|||
if (!string.IsNullOrEmpty(text)) |
|||
FireCommit(text); |
|||
} |
|||
} |
|||
|
|||
protected override Task Disconnect() => _context.DestroyAsync(); |
|||
|
|||
protected override void OnDisconnected() |
|||
{ |
|||
_context = null; |
|||
base.OnDisconnected(); |
|||
} |
|||
|
|||
protected override Task SetCursorRectCore(PixelRect rect) |
|||
=> _context.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height); |
|||
|
|||
protected override Task SetActiveCore(bool active) |
|||
=> active ? _context.FocusInAsync() : _context.FocusOutAsync(); |
|||
|
|||
protected override Task ResetContextCore() |
|||
=> _context.ResetAsync(); |
|||
|
|||
protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) |
|||
{ |
|||
IBusModifierMask state = default; |
|||
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control)) |
|||
state |= IBusModifierMask.ControlMask; |
|||
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt)) |
|||
state |= IBusModifierMask.Mod1Mask; |
|||
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift)) |
|||
state |= IBusModifierMask.ShiftMask; |
|||
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta)) |
|||
state |= IBusModifierMask.Mod4Mask; |
|||
|
|||
if (args.Type == RawKeyEventType.KeyUp) |
|||
state |= IBusModifierMask.ReleaseMask; |
|||
|
|||
return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state); |
|||
} |
|||
|
|||
public override void SetOptions(TextInputOptionsQueryEventArgs options) |
|||
{ |
|||
// No-op, because ibus
|
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue