// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Runtime.InteropServices;
namespace Microsoft.Maui.Platform.Linux.Services;
///
/// Factory for creating the appropriate Input Method service.
/// Automatically selects IBus or XIM based on availability.
///
public static class InputMethodServiceFactory
{
private static IInputMethodService? _instance;
private static readonly object _lock = new();
///
/// Gets the singleton input method service instance.
///
public static IInputMethodService Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
_instance ??= CreateService();
}
}
return _instance;
}
}
///
/// Creates the most appropriate input method service for the current environment.
///
public static IInputMethodService CreateService()
{
// Check environment variable for user preference
var imePreference = Environment.GetEnvironmentVariable("MAUI_INPUT_METHOD");
if (!string.IsNullOrEmpty(imePreference))
{
return imePreference.ToLowerInvariant() switch
{
"ibus" => CreateIBusService(),
"xim" => CreateXIMService(),
"none" => new NullInputMethodService(),
_ => CreateAutoService()
};
}
return CreateAutoService();
}
private static IInputMethodService CreateAutoService()
{
// Try IBus first (most common on modern Linux)
if (IsIBusAvailable())
{
Console.WriteLine("InputMethodServiceFactory: Using IBus");
return CreateIBusService();
}
// Fall back to XIM
if (IsXIMAvailable())
{
Console.WriteLine("InputMethodServiceFactory: Using XIM");
return CreateXIMService();
}
// No IME available
Console.WriteLine("InputMethodServiceFactory: No IME available, using null service");
return new NullInputMethodService();
}
private static IInputMethodService CreateIBusService()
{
try
{
return new IBusInputMethodService();
}
catch (Exception ex)
{
Console.WriteLine($"InputMethodServiceFactory: Failed to create IBus service - {ex.Message}");
return new NullInputMethodService();
}
}
private static IInputMethodService CreateXIMService()
{
try
{
return new X11InputMethodService();
}
catch (Exception ex)
{
Console.WriteLine($"InputMethodServiceFactory: Failed to create XIM service - {ex.Message}");
return new NullInputMethodService();
}
}
private static bool IsIBusAvailable()
{
// Check if IBus daemon is running
var ibusAddress = Environment.GetEnvironmentVariable("IBUS_ADDRESS");
if (!string.IsNullOrEmpty(ibusAddress))
{
return true;
}
// Try to load IBus library
try
{
var handle = NativeLibrary.Load("libibus-1.0.so.5");
NativeLibrary.Free(handle);
return true;
}
catch
{
return false;
}
}
private static bool IsXIMAvailable()
{
// Check XMODIFIERS environment variable
var xmodifiers = Environment.GetEnvironmentVariable("XMODIFIERS");
if (!string.IsNullOrEmpty(xmodifiers) && xmodifiers.Contains("@im="))
{
return true;
}
// Check if running under X11
var display = Environment.GetEnvironmentVariable("DISPLAY");
return !string.IsNullOrEmpty(display);
}
///
/// Resets the singleton instance (useful for testing).
///
public static void Reset()
{
lock (_lock)
{
_instance?.Shutdown();
_instance = null;
}
}
}
///
/// Null implementation of IInputMethodService for when no IME is available.
///
public class NullInputMethodService : IInputMethodService
{
public bool IsActive => false;
public string PreEditText => string.Empty;
public int PreEditCursorPosition => 0;
public event EventHandler? TextCommitted;
public event EventHandler? PreEditChanged;
public event EventHandler? PreEditEnded;
public void Initialize(nint windowHandle) { }
public void SetFocus(IInputContext? context) { }
public void SetCursorLocation(int x, int y, int width, int height) { }
public bool ProcessKeyEvent(uint keyCode, KeyModifiers modifiers, bool isKeyDown) => false;
public void Reset() { }
public void Shutdown() { }
}