| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Reflection;
- using System.Threading.Tasks;
- using Avalonia.FreeDesktop.DBusIme.Fcitx;
- using Avalonia.Input.Raw;
- using Avalonia.Input.TextInput;
- using Avalonia.Logging;
- using Tmds.DBus;
- namespace Avalonia.FreeDesktop.DBusIme
- {
- internal class DBusInputMethodFactory<T> : IX11InputMethodFactory where T : ITextInputMethodImpl, IX11InputMethodControl
- {
- private readonly Func<IntPtr, T> _factory;
- public DBusInputMethodFactory(Func<IntPtr, T> factory)
- {
- _factory = factory;
- }
- public (ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid)
- {
- var im = _factory(xid);
- return (im, im);
- }
- }
-
- internal abstract class DBusTextInputMethodBase : IX11InputMethodControl, ITextInputMethodImpl
- {
- private List<IDisposable> _disposables = new List<IDisposable>();
- private Queue<string> _onlineNamesQueue = new Queue<string>();
- protected Connection Connection { get; }
- private readonly string[] _knownNames;
- private bool _connecting;
- private string _currentName;
- private DBusCallQueue _queue;
- private bool _controlActive, _windowActive;
- private bool? _imeActive;
- private Rect _logicalRect;
- private PixelRect? _lastReportedRect;
- private double _scaling = 1;
- private PixelPoint _windowPosition;
-
- protected bool IsConnected => _currentName != null;
-
- public DBusTextInputMethodBase(Connection connection, params string[] knownNames)
- {
- _queue = new DBusCallQueue(QueueOnError);
- Connection = connection;
- _knownNames = knownNames;
- Watch();
- }
- async void Watch()
- {
- foreach (var name in _knownNames)
- _disposables.Add(await Connection.ResolveServiceOwnerAsync(name, OnNameChange));
- }
-
- protected abstract Task<bool> Connect(string name);
- protected string GetAppName() =>
- Application.Current.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia";
-
- private async void OnNameChange(ServiceOwnerChangedEventArgs args)
- {
- if (args.NewOwner != null && _currentName == null)
- {
- _onlineNamesQueue.Enqueue(args.ServiceName);
- if(!_connecting)
- {
- _connecting = true;
- try
- {
- while (_onlineNamesQueue.Count > 0)
- {
- var name = _onlineNamesQueue.Dequeue();
- try
- {
- if (await Connect(name))
- {
- _onlineNamesQueue.Clear();
- _currentName = name;
- return;
- }
- }
- catch (Exception e)
- {
- Logger.TryGet(LogEventLevel.Error, "IME")
- ?.Log(this, "Unable to create IME input context:\n" + e);
- }
- }
- }
- finally
- {
- _connecting = false;
- }
- }
-
- }
-
- // IME has crashed
- if (args.NewOwner == null && args.ServiceName == _currentName)
- {
- _currentName = null;
- foreach(var s in _disposables)
- s.Dispose();
- _disposables.Clear();
-
- OnDisconnected();
- Reset();
-
- // Watch again
- Watch();
- }
- }
-
- protected virtual Task Disconnect()
- {
- return Task.CompletedTask;
- }
- protected virtual void OnDisconnected()
- {
-
- }
- protected virtual void Reset()
- {
- _lastReportedRect = null;
- _imeActive = null;
- }
- async Task QueueOnError(Exception e)
- {
- Logger.TryGet(LogEventLevel.Error, "IME")
- ?.Log(this, "Error:\n" + e);
- try
- {
- await Disconnect();
- }
- catch (Exception ex)
- {
- Logger.TryGet(LogEventLevel.Error, "IME")
- ?.Log(this, "Error while destroying the context:\n" + ex);
- }
- OnDisconnected();
- _currentName = null;
- }
-
- protected void Enqueue(Func<Task> cb) => _queue.Enqueue(cb);
- protected void AddDisposable(IDisposable d) => _disposables.Add(d);
-
- public void Dispose()
- {
- foreach(var d in _disposables)
- d.Dispose();
- _disposables.Clear();
- try
- {
- Disconnect().ContinueWith(_ => { });
- }
- catch
- {
- // fire and forget
- }
- _currentName = null;
- }
- protected abstract Task SetCursorRectCore(PixelRect rect);
- protected abstract Task SetActiveCore(bool active);
- protected abstract Task ResetContextCore();
- protected abstract Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode);
- void UpdateActive()
- {
- _queue.Enqueue(async () =>
- {
- if(!IsConnected)
- return;
-
- var active = _windowActive && _controlActive;
- if (active != _imeActive)
- {
- _imeActive = active;
- await SetActiveCore(active);
- }
- });
- }
- void IX11InputMethodControl.SetWindowActive(bool active)
- {
- _windowActive = active;
- UpdateActive();
- }
-
- void ITextInputMethodImpl.SetActive(bool active)
- {
- _controlActive = active;
- UpdateActive();
- }
- bool IX11InputMethodControl.IsEnabled => IsConnected && _imeActive == true;
- async ValueTask<bool> IX11InputMethodControl.HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode)
- {
- try
- {
- return await _queue.EnqueueAsync(async () => await HandleKeyCore(args, keyVal, keyCode));
- }
- // Disconnected
- catch (OperationCanceledException)
- {
- return false;
- }
- // Error, disconnect
- catch (Exception e)
- {
- await QueueOnError(e);
- return false;
- }
- }
- private Action<string> _onCommit;
- event Action<string> IX11InputMethodControl.Commit
- {
- add => _onCommit += value;
- remove => _onCommit -= value;
- }
- protected void FireCommit(string s) => _onCommit?.Invoke(s);
-
- private Action<X11InputMethodForwardedKey> _onForward;
- event Action<X11InputMethodForwardedKey> IX11InputMethodControl.ForwardKey
- {
- add => _onForward += value;
- remove => _onForward -= value;
- }
- protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k);
-
- void UpdateCursorRect()
- {
- _queue.Enqueue(async () =>
- {
- if(!IsConnected)
- return;
- var cursorRect = PixelRect.FromRect(_logicalRect, _scaling);
- cursorRect = cursorRect.Translate(_windowPosition);
- if (cursorRect != _lastReportedRect)
- {
- _lastReportedRect = cursorRect;
- await SetCursorRectCore(cursorRect);
- }
- });
- }
-
- void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling)
- {
- _windowPosition = position;
- _scaling = scaling;
- UpdateCursorRect();
- }
- void ITextInputMethodImpl.SetCursorRect(Rect rect)
- {
- _logicalRect = rect;
- UpdateCursorRect();
- }
- public abstract void SetOptions(TextInputOptionsQueryEventArgs options);
- void ITextInputMethodImpl.Reset()
- {
- Reset();
- _queue.Enqueue(async () =>
- {
- if (!IsConnected)
- return;
- await ResetContextCore();
- });
- }
- }
- }
|