2025-12-19 09:30:16 +00:00
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Factory for creating the appropriate Input Method service.
|
|
|
|
|
/// Automatically selects IBus or XIM based on availability.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static class InputMethodServiceFactory
|
|
|
|
|
{
|
|
|
|
|
private static IInputMethodService? _instance;
|
|
|
|
|
private static readonly object _lock = new();
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the singleton input method service instance.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static IInputMethodService Instance
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_instance == null)
|
|
|
|
|
{
|
|
|
|
|
lock (_lock)
|
|
|
|
|
{
|
|
|
|
|
_instance ??= CreateService();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return _instance;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates the most appropriate input method service for the current environment.
|
|
|
|
|
/// </summary>
|
|
|
|
|
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(),
|
2025-12-28 09:53:40 -05:00
|
|
|
"fcitx" or "fcitx5" => CreateFcitx5Service(),
|
2025-12-19 09:30:16 +00:00
|
|
|
"xim" => CreateXIMService(),
|
|
|
|
|
"none" => new NullInputMethodService(),
|
|
|
|
|
_ => CreateAutoService()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CreateAutoService();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IInputMethodService CreateAutoService()
|
|
|
|
|
{
|
2025-12-28 09:53:40 -05:00
|
|
|
// Check GTK_IM_MODULE for hint
|
|
|
|
|
var imModule = Environment.GetEnvironmentVariable("GTK_IM_MODULE")?.ToLowerInvariant();
|
|
|
|
|
|
|
|
|
|
// Try Fcitx5 first if it's the configured IM
|
|
|
|
|
if (imModule?.Contains("fcitx") == true && Fcitx5InputMethodService.IsAvailable())
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Debug("InputMethodServiceFactory", "Using Fcitx5");
|
2025-12-28 09:53:40 -05:00
|
|
|
return CreateFcitx5Service();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try IBus (most common on modern Linux)
|
2025-12-19 09:30:16 +00:00
|
|
|
if (IsIBusAvailable())
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Debug("InputMethodServiceFactory", "Using IBus");
|
2025-12-19 09:30:16 +00:00
|
|
|
return CreateIBusService();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 09:53:40 -05:00
|
|
|
// Try Fcitx5 as fallback
|
|
|
|
|
if (Fcitx5InputMethodService.IsAvailable())
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Debug("InputMethodServiceFactory", "Using Fcitx5");
|
2025-12-28 09:53:40 -05:00
|
|
|
return CreateFcitx5Service();
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
// Fall back to XIM
|
|
|
|
|
if (IsXIMAvailable())
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Debug("InputMethodServiceFactory", "Using XIM");
|
2025-12-19 09:30:16 +00:00
|
|
|
return CreateXIMService();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No IME available
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Warn("InputMethodServiceFactory", "No IME available, using null service");
|
2025-12-19 09:30:16 +00:00
|
|
|
return new NullInputMethodService();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static IInputMethodService CreateIBusService()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return new IBusInputMethodService();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Error("InputMethodServiceFactory", $"Failed to create IBus service - {ex.Message}");
|
2025-12-19 09:30:16 +00:00
|
|
|
return new NullInputMethodService();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 09:53:40 -05:00
|
|
|
private static IInputMethodService CreateFcitx5Service()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return new Fcitx5InputMethodService();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Error("InputMethodServiceFactory", $"Failed to create Fcitx5 service - {ex.Message}");
|
2025-12-28 09:53:40 -05:00
|
|
|
return new NullInputMethodService();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-19 09:30:16 +00:00
|
|
|
private static IInputMethodService CreateXIMService()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return new X11InputMethodService();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
2026-03-06 22:06:08 -05:00
|
|
|
DiagnosticLog.Error("InputMethodServiceFactory", $"Failed to create XIM service - {ex.Message}");
|
2025-12-19 09:30:16 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Resets the singleton instance (useful for testing).
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static void Reset()
|
|
|
|
|
{
|
|
|
|
|
lock (_lock)
|
|
|
|
|
{
|
|
|
|
|
_instance?.Shutdown();
|
|
|
|
|
_instance = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|