Nikita Tsukanov 5 years ago
parent
commit
e1f0ce2c31
  1. 3
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  2. 5
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  3. 52
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs
  4. 45
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs
  5. 105
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  6. 39
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  7. 44
      src/Avalonia.X11/X11Platform.cs

3
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -60,6 +60,9 @@ namespace Avalonia.FreeDesktop.DBusIme
}
protected abstract Task<bool> Connect(string name);
protected string GetAppName() =>
Application.Current.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia";
private async void OnNameChange(ServiceOwnerChangedEventArgs args)
{

5
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

@ -25,11 +25,10 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
protected override async Task<bool> Connect(string name)
{
var appName = Application.Current.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia";
if (name == "org.fcitx.Fcitx")
{
var method = Connection.CreateProxy<IFcitxInputMethod>(name, "/inputmethod");
var resp = await method.CreateICv3Async(appName,
var resp = await method.CreateICv3Async(GetAppName(),
Process.GetCurrentProcess().Id);
var proxy = Connection.CreateProxy<IFcitxInputContext>(name,
@ -40,7 +39,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
else
{
var method = Connection.CreateProxy<IFcitxInputMethod1>(name, "/inputmethod");
var resp = await method.CreateInputContextAsync(new[] { ("appName", appName) });
var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) });
var proxy = Connection.CreateProxy<IFcitxInputContext1>(name, resp.path);
_context = new FcitxICWrapper(proxy);
}

52
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs

@ -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);
}
}

45
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs

@ -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,
}
}

105
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

@ -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
}
}
}

39
src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.FreeDesktop.DBusIme.Fcitx;
using Avalonia.FreeDesktop.DBusIme.IBus;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme
@ -11,24 +12,20 @@ namespace Avalonia.FreeDesktop.DBusIme
new Dictionary<string, Func<Connection, IX11InputMethodFactory>>
{
["fcitx"] = conn =>
new DBusInputMethodFactory<FcitxX11TextInputMethod>(_ => new FcitxX11TextInputMethod(conn))
new DBusInputMethodFactory<FcitxX11TextInputMethod>(_ => new FcitxX11TextInputMethod(conn)),
["ibus"] = conn =>
new DBusInputMethodFactory<IBusX11TextInputMethod>(_ => new IBusX11TextInputMethod(conn))
};
static bool IsCjkLocale(string lang)
{
if (lang == null)
return false;
return lang.Contains("zh")
|| lang.Contains("ja")
|| lang.Contains("vi")
|| lang.Contains("ko");
}
static Func<Connection, IX11InputMethodFactory> DetectInputMethod()
{
foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" })
{
var value = Environment.GetEnvironmentVariable(name);
if (value == "none")
return null;
if (value != null && KnownMethods.TryGetValue(value, out var factory))
return factory;
}
@ -36,22 +33,16 @@ namespace Avalonia.FreeDesktop.DBusIme
return null;
}
public static bool RegisterIfNeeded(bool? optionsWantIme)
public static bool DetectAndRegister()
{
if(
optionsWantIme == true
|| Environment.GetEnvironmentVariable("AVALONIA_FORCE_IME") == "1"
|| (optionsWantIme == null && IsCjkLocale(Environment.GetEnvironmentVariable("LANG"))))
var factory = DetectInputMethod();
if (factory != null)
{
var factory = DetectInputMethod();
if (factory != null)
var conn = DBusHelper.TryInitialize();
if (conn != null)
{
var conn = DBusHelper.TryInitialize();
if (conn != null)
{
AvaloniaLocator.CurrentMutable.Bind<IX11InputMethodFactory>().ToConstant(factory(conn));
return true;
}
AvaloniaLocator.CurrentMutable.Bind<IX11InputMethodFactory>().ToConstant(factory(conn));
return true;
}
}

44
src/Avalonia.X11/X11Platform.cs

@ -39,9 +39,13 @@ namespace Avalonia.X11
Options = options;
bool useXim = false;
if (!X11DBusImeHelper.RegisterIfNeeded(Options.EnableIme))
useXim = ShouldUseXim();
if (EnableIme(options))
{
// Attempt to configure DBus-based input method and check if we can fall back to XIM
if (!X11DBusImeHelper.DetectAndRegister() && ShouldUseXim())
useXim = true;
}
// XIM doesn't work at all otherwise
if (useXim)
setlocale(0, "");
@ -106,8 +110,36 @@ namespace Avalonia.X11
throw new NotSupportedException();
}
bool EnableIme(X11PlatformOptions options)
{
// Disable if explicitly asked by user
var avaloniaImModule = Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE");
if (avaloniaImModule == "none")
return false;
// Use value from options when specified
if (options.EnableIme.HasValue)
return options.EnableIme.Value;
// Automatically enable for CJK locales
var lang = Environment.GetEnvironmentVariable("LANG");
var isCjkLocale = lang != null &&
(lang.Contains("zh")
|| lang.Contains("ja")
|| lang.Contains("vi")
|| lang.Contains("ko"));
return isCjkLocale;
}
bool ShouldUseXim()
{
// Check if we are forbidden from using IME
if (Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE") == "none"
|| Environment.GetEnvironmentVariable("GTK_IM_MODULE") == "none"
|| Environment.GetEnvironmentVariable("QT_IM_MODULE") == "none")
return true;
// Check if XIM is configured
var modifiers = Environment.GetEnvironmentVariable("XMODIFIERS");
if (modifiers == null)
@ -122,11 +154,6 @@ namespace Avalonia.X11
|| Environment.GetEnvironmentVariable("QT_IM_MODULE") == "xim"
|| Environment.GetEnvironmentVariable("AVALONIA_IM_MODULE") == "xim")
return true;
// Check if fallback is enabled
if (Options.ForceEnableXimFallback ||
Environment.GetEnvironmentVariable("AVALONIA_FORCE_XIM_FALLBACK") == "1")
return true;
return false;
}
@ -144,7 +171,6 @@ namespace Avalonia
public bool UseDBusMenu { get; set; }
public bool UseDeferredRendering { get; set; } = true;
public bool? EnableIme { get; set; }
public bool ForceEnableXimFallback { get; set; }
public IList<GlVersion> GlProfiles { get; set; } = new List<GlVersion>
{

Loading…
Cancel
Save