// 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(), "fcitx" or "fcitx5" => CreateFcitx5Service(), "xim" => CreateXIMService(), "none" => new NullInputMethodService(), _ => CreateAutoService() }; } return CreateAutoService(); } private static IInputMethodService CreateAutoService() { // 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()) { DiagnosticLog.Debug("InputMethodServiceFactory", "Using Fcitx5"); return CreateFcitx5Service(); } // Try IBus (most common on modern Linux) if (IsIBusAvailable()) { DiagnosticLog.Debug("InputMethodServiceFactory", "Using IBus"); return CreateIBusService(); } // Try Fcitx5 as fallback if (Fcitx5InputMethodService.IsAvailable()) { DiagnosticLog.Debug("InputMethodServiceFactory", "Using Fcitx5"); return CreateFcitx5Service(); } // Fall back to XIM if (IsXIMAvailable()) { DiagnosticLog.Debug("InputMethodServiceFactory", "Using XIM"); return CreateXIMService(); } // No IME available DiagnosticLog.Warn("InputMethodServiceFactory", "No IME available, using null service"); return new NullInputMethodService(); } private static IInputMethodService CreateIBusService() { try { return new IBusInputMethodService(); } catch (Exception ex) { DiagnosticLog.Error("InputMethodServiceFactory", $"Failed to create IBus service - {ex.Message}"); return new NullInputMethodService(); } } private static IInputMethodService CreateFcitx5Service() { try { return new Fcitx5InputMethodService(); } catch (Exception ex) { DiagnosticLog.Error("InputMethodServiceFactory", $"Failed to create Fcitx5 service - {ex.Message}"); return new NullInputMethodService(); } } private static IInputMethodService CreateXIMService() { try { return new X11InputMethodService(); } catch (Exception ex) { DiagnosticLog.Error("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; } } }