From 1ed47c608c7d9f3ba3e5f852f7e261ac5ba9e85e Mon Sep 17 00:00:00 2001 From: Mikkel Kruse Johnsen Date: Wed, 10 Jan 2024 13:55:58 +0100 Subject: [PATCH 1/2] Make Mono.Addin load i18n dynamically depending on the os. For Linux libc should be used and for Windows libintl-8.dll and for macOS libintl.8.dylib. Made it possible to set a base locale dir for where the localizer shoudl look. --- .../Mono.Addins.Localization/FuncLoader.cs | 117 ++++++++++++++++++ .../Mono.Addins.Localization/GLibrary.cs | 90 ++++++++++++++ .../Mono.Addins.Localization/GettextDomain.cs | 29 +++-- .../GettextLocalizer.cs | 2 +- .../Mono.Addins.Localization/Library.cs | 5 + Mono.Addins/Mono.Addins/RuntimeAddin.cs | 20 +++ 6 files changed, 251 insertions(+), 12 deletions(-) create mode 100644 Mono.Addins/Mono.Addins.Localization/FuncLoader.cs create mode 100644 Mono.Addins/Mono.Addins.Localization/GLibrary.cs create mode 100644 Mono.Addins/Mono.Addins.Localization/Library.cs diff --git a/Mono.Addins/Mono.Addins.Localization/FuncLoader.cs b/Mono.Addins/Mono.Addins.Localization/FuncLoader.cs new file mode 100644 index 00000000..29c699cb --- /dev/null +++ b/Mono.Addins/Mono.Addins.Localization/FuncLoader.cs @@ -0,0 +1,117 @@ +using System; +using System.Runtime.InteropServices; + +class FuncLoader +{ + private class Windows + { + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr LoadLibraryW(string lpszLib); + } + + private class Linux + { + [DllImport("libdl.so.2")] + public static extern IntPtr dlopen(string path, int flags); + + [DllImport("libdl.so.2")] + public static extern IntPtr dlsym(IntPtr handle, string symbol); + } + + private class OSX + { + [DllImport("/usr/lib/libSystem.dylib")] + public static extern IntPtr dlopen(string path, int flags); + + [DllImport("/usr/lib/libSystem.dylib")] + public static extern IntPtr dlsym(IntPtr handle, string symbol); + } + + private class Unix + { + [DllImport("libc")] + public static extern IntPtr dlopen(string path, int flags); + + [DllImport("libc")] + public static extern IntPtr dlsym(IntPtr handle, string symbol); + } + + [DllImport("libc")] + private static extern int uname(IntPtr buf); + + private const int RTLD_LAZY = 0x0001; + private const int RTLD_GLOBAL = 0x0100; + + public static bool IsWindows, IsOSX, IsLinux; + + static FuncLoader() + { + switch (Environment.OSVersion.Platform) + { + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + IsWindows = true; + break; + case PlatformID.MacOSX: + IsOSX = true; + break; + case PlatformID.Unix: + try + { + var buf = Marshal.AllocHGlobal(8192); + if (uname(buf) == 0 && Marshal.PtrToStringAnsi(buf) == "Darwin") + IsOSX = true; + if (uname(buf) == 0 && Marshal.PtrToStringAnsi(buf) == "Linux") + IsLinux = true; + + Marshal.FreeHGlobal(buf); + } + catch { } + + break; + } + } + + public static IntPtr LoadLibrary(string libname) + { + if (IsWindows) + return Windows.LoadLibraryW(libname); + + if (IsOSX) + return OSX.dlopen(libname, RTLD_GLOBAL | RTLD_LAZY); + + if (IsLinux) + return Linux.dlopen(libname, RTLD_GLOBAL | RTLD_LAZY); + + return Unix.dlopen(libname, RTLD_GLOBAL | RTLD_LAZY); + } + + public static IntPtr GetProcAddress(IntPtr library, string function) + { + var ret = IntPtr.Zero; + + if (IsWindows) + ret = Windows.GetProcAddress(library, function); + else if (IsOSX) + ret = OSX.dlsym(library, function); + else if (IsLinux) + ret = Linux.dlsym(library, function); + else + ret = Unix.dlsym(library, function); + + return ret; + } + + public static T LoadFunction(IntPtr procaddress) + { + if (procaddress == IntPtr.Zero) + return default(T); + + return Marshal.GetDelegateForFunctionPointer(procaddress); + } +} diff --git a/Mono.Addins/Mono.Addins.Localization/GLibrary.cs b/Mono.Addins/Mono.Addins.Localization/GLibrary.cs new file mode 100644 index 00000000..aba65510 --- /dev/null +++ b/Mono.Addins/Mono.Addins.Localization/GLibrary.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +class GLibrary +{ + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool SetDllDirectory(string lpPathName); + + private static Dictionary _libraries; + private static HashSet _librariesNotFound; + private static Dictionary _customlibraries; + private static Dictionary _libraryDefinitions; + + static GLibrary() + { + _customlibraries = new Dictionary(); + _librariesNotFound = new HashSet(); + _libraries = new Dictionary(); + _libraryDefinitions = new Dictionary(); + _libraryDefinitions[Library.Intl] = new[] {"libintl-8.dll", "libc.so.6", "libintl.8.dylib", "libintl-8.dll"}; + } + + public static IntPtr Load(Library library) + { + if (_libraries.TryGetValue(library, out var ret)) + return ret; + + if (TryGet(library, out ret)) return ret; + + var err = library + ": " + string.Join(", ", _libraryDefinitions[library]); + + throw new DllNotFoundException(err); + + } + + public static bool IsSupported(Library library) => TryGet(library, out var __); + + static bool TryGet(Library library, out IntPtr ret) + { + ret = IntPtr.Zero; + + if (_libraries.TryGetValue(library, out ret)) { + return true; + } + + if (_librariesNotFound.Contains(library)) { + return false; + } + + if (FuncLoader.IsWindows) { + ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][0]); + + if (ret == IntPtr.Zero) { + SetDllDirectory(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); + ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][0]); + } + } else if (FuncLoader.IsOSX) { + ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][2]); + + if (ret == IntPtr.Zero) { + ret = FuncLoader.LoadLibrary("/usr/local/lib/" + _libraryDefinitions[library][2]); + if (ret == IntPtr.Zero) { + ret = FuncLoader.LoadLibrary("/opt/homebrew/lib/" + _libraryDefinitions[library][2]); + } + } + } else + ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][1]); + + if (ret == IntPtr.Zero) { + for (var i = 0; i < _libraryDefinitions[library].Length; i++) { + ret = FuncLoader.LoadLibrary(_libraryDefinitions[library][i]); + + if (ret != IntPtr.Zero) + break; + } + } + + if (ret != IntPtr.Zero) { + _libraries[library] = ret; + } else { + _librariesNotFound.Add(library); + } + + return ret != IntPtr.Zero; + } + +} diff --git a/Mono.Addins/Mono.Addins.Localization/GettextDomain.cs b/Mono.Addins/Mono.Addins.Localization/GettextDomain.cs index e31b16ea..ee937016 100644 --- a/Mono.Addins/Mono.Addins.Localization/GettextDomain.cs +++ b/Mono.Addins/Mono.Addins.Localization/GettextDomain.cs @@ -39,15 +39,22 @@ namespace Mono.Addins.Localization { class GettextDomain { - [DllImport("intl", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr bindtextdomain (IntPtr domainname, IntPtr dirname); - [DllImport("intl", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr bind_textdomain_codeset (IntPtr domainname, IntPtr codeset); - [DllImport("intl", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr dgettext (IntPtr domainname, IntPtr instring); - [DllImport("intl", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr dngettext (IntPtr domainname, IntPtr instring, IntPtr plural, int n); - + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + delegate IntPtr d_bindtextdomain (IntPtr domainname, IntPtr dirname); + static d_bindtextdomain bindtextdomain = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "bindtextdomain")); + + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + delegate IntPtr d_bind_textdomain_codeset (IntPtr domainname, IntPtr codeset); + static d_bind_textdomain_codeset bind_textdomain_codeset = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "bind_textdomain_codeset")); + + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + delegate IntPtr d_dgettext (IntPtr domainname, IntPtr instring); + static d_dgettext dgettext = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "dgettext")); + + [UnmanagedFunctionPointer (CallingConvention.Cdecl)] + delegate IntPtr d_dngettext (IntPtr domainname, IntPtr instring, IntPtr plural, int n); + static d_dngettext dngettext = FuncLoader.LoadFunction(FuncLoader.GetProcAddress(GLibrary.Load(Library.Intl), "dngettext")); + IntPtr ipackage; public void Init (String package, string localedir) @@ -58,11 +65,11 @@ public void Init (String package, string localedir) string prefix = f.Directory.Parent.Parent.Parent.ToString (); prefix = Path.Combine (Path.Combine (prefix, "share"), "locale"); } - + ipackage = StringToPtr (package); IntPtr ilocaledir = StringToPtr (localedir); IntPtr iutf8 = StringToPtr ("UTF-8"); - + try { if (bindtextdomain (ipackage, ilocaledir) == IntPtr.Zero) throw new InvalidOperationException ("Gettext localizer: bindtextdomain failed"); diff --git a/Mono.Addins/Mono.Addins.Localization/GettextLocalizer.cs b/Mono.Addins/Mono.Addins.Localization/GettextLocalizer.cs index 56a27749..4ab57749 100644 --- a/Mono.Addins/Mono.Addins.Localization/GettextLocalizer.cs +++ b/Mono.Addins/Mono.Addins.Localization/GettextLocalizer.cs @@ -42,7 +42,7 @@ public IAddinLocalizer CreateLocalizer (RuntimeAddin addin, NodeElement element) string dir = element.GetAttribute ("location"); if (dir.Length == 0) dir = "locale"; - dir = addin.GetFilePath (dir); + dir = addin.GetLocaleFilePath (dir); domain = new GettextDomain (); domain.Init (pkg, dir); return this; diff --git a/Mono.Addins/Mono.Addins.Localization/Library.cs b/Mono.Addins/Mono.Addins.Localization/Library.cs new file mode 100644 index 00000000..6fb3091e --- /dev/null +++ b/Mono.Addins/Mono.Addins.Localization/Library.cs @@ -0,0 +1,5 @@ + +enum Library +{ + Intl, +} diff --git a/Mono.Addins/Mono.Addins/RuntimeAddin.cs b/Mono.Addins/Mono.Addins/RuntimeAddin.cs index abbbf1f4..e5dd76a3 100644 --- a/Mono.Addins/Mono.Addins/RuntimeAddin.cs +++ b/Mono.Addins/Mono.Addins/RuntimeAddin.cs @@ -50,6 +50,7 @@ namespace Mono.Addins public class RuntimeAddin { readonly string id; + readonly string localeBaseDirectory; readonly string baseDirectory; readonly Addin ainfo; readonly RuntimeAddin parentAddin; @@ -75,6 +76,7 @@ internal RuntimeAddin (AddinEngine addinEngine, Addin iad) AddinDescription description = iad.Description; id = description.AddinId; baseDirectory = description.BasePath; + localeBaseDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? description.BasePath; module = description.MainModule; module.RuntimeAddin = this; localizerDescription = description.Localizer; @@ -87,6 +89,7 @@ internal RuntimeAddin (AddinEngine addinEngine, RuntimeAddin parentAddin, Module this.module = module; id = parentAddin.id; baseDirectory = parentAddin.baseDirectory; + localeBaseDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? parentAddin.baseDirectory; privatePath = parentAddin.privatePath; ainfo = parentAddin.ainfo; module.RuntimeAddin = this; @@ -480,6 +483,23 @@ public string GetFilePath (string fileName) return Path.Combine (baseDirectory, fileName); } + /// + /// Gets the path of the locale directory for the add-in file. + /// + /// + /// The directory relative path of the file or if env MONO_ADDINS_BASE_LOCALE_DIR is set. + /// + /// + /// Full path of the directory + /// + /// + /// This method can be used to get the full path of the directory for the localization. + /// + public string GetLocaleFilePath (string dirName) + { + return Path.Combine (localeBaseDirectory, dirName); + } + /// /// Gets the path of an add-in file /// From fe33af1e1073ca1e95c2a081c31bf799b17ddb2d Mon Sep 17 00:00:00 2001 From: Mikkel Kruse Johnsen Date: Thu, 11 Jan 2024 11:05:39 +0100 Subject: [PATCH 2/2] Set fallback library to 'intl' as it used to be --- Mono.Addins/Mono.Addins.Localization/GLibrary.cs | 2 +- Mono.Addins/Mono.Addins/RuntimeAddin.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Mono.Addins/Mono.Addins.Localization/GLibrary.cs b/Mono.Addins/Mono.Addins.Localization/GLibrary.cs index aba65510..40adeda8 100644 --- a/Mono.Addins/Mono.Addins.Localization/GLibrary.cs +++ b/Mono.Addins/Mono.Addins.Localization/GLibrary.cs @@ -20,7 +20,7 @@ static GLibrary() _librariesNotFound = new HashSet(); _libraries = new Dictionary(); _libraryDefinitions = new Dictionary(); - _libraryDefinitions[Library.Intl] = new[] {"libintl-8.dll", "libc.so.6", "libintl.8.dylib", "libintl-8.dll"}; + _libraryDefinitions[Library.Intl] = new[] {"libintl-8.dll", "libc.so.6", "libintl.8.dylib", "intl"}; } public static IntPtr Load(Library library) diff --git a/Mono.Addins/Mono.Addins/RuntimeAddin.cs b/Mono.Addins/Mono.Addins/RuntimeAddin.cs index e5dd76a3..f95e29cf 100644 --- a/Mono.Addins/Mono.Addins/RuntimeAddin.cs +++ b/Mono.Addins/Mono.Addins/RuntimeAddin.cs @@ -50,7 +50,7 @@ namespace Mono.Addins public class RuntimeAddin { readonly string id; - readonly string localeBaseDirectory; + readonly string baseLocaleDirectory; readonly string baseDirectory; readonly Addin ainfo; readonly RuntimeAddin parentAddin; @@ -76,7 +76,7 @@ internal RuntimeAddin (AddinEngine addinEngine, Addin iad) AddinDescription description = iad.Description; id = description.AddinId; baseDirectory = description.BasePath; - localeBaseDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? description.BasePath; + baseLocaleDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? description.BasePath; module = description.MainModule; module.RuntimeAddin = this; localizerDescription = description.Localizer; @@ -89,7 +89,7 @@ internal RuntimeAddin (AddinEngine addinEngine, RuntimeAddin parentAddin, Module this.module = module; id = parentAddin.id; baseDirectory = parentAddin.baseDirectory; - localeBaseDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? parentAddin.baseDirectory; + baseLocaleDirectory = Environment.GetEnvironmentVariable ("MONO_ADDINS_BASE_LOCALE_DIR") ?? parentAddin.baseDirectory; privatePath = parentAddin.privatePath; ainfo = parentAddin.ainfo; module.RuntimeAddin = this; @@ -497,7 +497,7 @@ public string GetFilePath (string fileName) /// public string GetLocaleFilePath (string dirName) { - return Path.Combine (localeBaseDirectory, dirName); + return Path.Combine (baseLocaleDirectory, dirName); } ///