--- a/KeePass/KeePass.csproj +++ b/KeePass/KeePass.csproj @@ -181,6 +181,9 @@ KeePassLib\Native\NativeMethods.cs + + KeePassLib\Native\NativeMethods.Unix.cs + KeePassLib\PwCustomIcon.cs @@ -1006,7 +1009,9 @@ - + + KeePassLib\Utility\MonoWorkarounds.cs + --- a/KeePass/UI/CustomListViewEx.cs +++ b/KeePass/UI/CustomListViewEx.cs @@ -26,6 +26,7 @@ using KeePass.Native; using KeePass.Util; +using KeePassLib.Utility; namespace KeePass.UI { --- a/KeePass/Util/MonoWorkarounds.cs +++ /dev/null @@ -1,280 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows.Forms; -using System.ComponentModel; -using System.Reflection; -using System.Diagnostics; - -using KeePassLib; -using KeePassLib.Native; - -namespace KeePass.Util -{ - public static class MonoWorkarounds - { - private static bool? m_bReq = null; - public static bool IsRequired() - { - if(!m_bReq.HasValue) m_bReq = NativeLib.IsUnix(); - return m_bReq.Value; - } - - // 5795: - // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 - // https://sourceforge.net/p/keepass/discussion/329220/thread/d23dc88b/ - // 12525: - // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 - // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ - // 586901: - // https://bugzilla.novell.com/show_bug.cgi?id=586901 - // 620618: - // https://bugzilla.novell.com/show_bug.cgi?id=620618 - // 649266: - // https://bugzilla.novell.com/show_bug.cgi?id=649266 - // 686017: - // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 - // 801414: - // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 - // 891029: - // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 - // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 - // 836428016: - // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ - // 3574233558: - // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ - public static bool IsRequired(uint uBugID) - { - return MonoWorkarounds.IsRequired(); - } - - public static void ApplyTo(Form f) - { - if(!MonoWorkarounds.IsRequired()) return; - if(f == null) { Debug.Assert(false); return; } - - f.HandleCreated += MonoWorkarounds.OnFormHandleCreated; - SetWmClass(f); - - ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ApplyToControl); - } - - public static void Release(Form f) - { - if(!MonoWorkarounds.IsRequired()) return; - if(f == null) { Debug.Assert(false); return; } - - f.HandleCreated -= MonoWorkarounds.OnFormHandleCreated; - - ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ReleaseControl); - } - - private delegate void MwaControlHandler(Control c, Form fContext); - - private static void ApplyToControlsRec(Control.ControlCollection cc, - Form fContext, MwaControlHandler fn) - { - if(cc == null) { Debug.Assert(false); return; } - - foreach(Control c in cc) - { - fn(c, fContext); - ApplyToControlsRec(c.Controls, fContext, fn); - } - } - - private sealed class MwaHandlerInfo - { - private readonly Delegate m_fnOrg; // May be null - public Delegate FunctionOriginal - { - get { return m_fnOrg; } - } - - private readonly Delegate m_fnOvr; - public Delegate FunctionOverride - { - get { return m_fnOvr; } - } - - private readonly DialogResult m_dr; - public DialogResult Result - { - get { return m_dr; } - } - - private readonly Form m_fContext; - public Form FormContext - { - get { return m_fContext; } - } - - public MwaHandlerInfo(Delegate fnOrg, Delegate fnOvr, DialogResult dr, - Form fContext) - { - m_fnOrg = fnOrg; - m_fnOvr = fnOvr; - m_dr = dr; - m_fContext = fContext; - } - } - - private static void ApplyToControl(Control c, Form fContext) - { - Button btn = (c as Button); - if(btn != null) ApplyToButton(btn, fContext); - } - - private static EventHandlerList GetEventHandlers(Component c, - out object objClickEvent) - { - FieldInfo fi = typeof(Control).GetField("ClickEvent", // Mono - BindingFlags.Static | BindingFlags.NonPublic); - if(fi == null) - fi = typeof(Control).GetField("EventClick", // .NET - BindingFlags.Static | BindingFlags.NonPublic); - if(fi == null) { Debug.Assert(false); objClickEvent = null; return null; } - - objClickEvent = fi.GetValue(null); - if(objClickEvent == null) { Debug.Assert(false); return null; } - - PropertyInfo pi = typeof(Component).GetProperty("Events", - BindingFlags.Instance | BindingFlags.NonPublic); - return (pi.GetValue(c, null) as EventHandlerList); - } - - private static Dictionary m_dictHandlers = - new Dictionary(); - private static void ApplyToButton(Button btn, Form fContext) - { - DialogResult dr = btn.DialogResult; - if(dr == DialogResult.None) return; // No workaround required - - object objClickEvent; - EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); - if(ehl == null) { Debug.Assert(false); return; } - Delegate fnClick = ehl[objClickEvent]; // May be null - - EventHandler fnOvr = new EventHandler(MonoWorkarounds.OnButtonClick); - m_dictHandlers[btn] = new MwaHandlerInfo(fnClick, fnOvr, dr, fContext); - - btn.DialogResult = DialogResult.None; - if(fnClick != null) ehl.RemoveHandler(objClickEvent, fnClick); - ehl[objClickEvent] = fnOvr; - } - - private static void ReleaseControl(Control c, Form fContext) - { - Button btn = (c as Button); - if(btn != null) ReleaseButton(btn, fContext); - } - - private static void ReleaseButton(Button btn, Form fContext) - { - MwaHandlerInfo hi; - m_dictHandlers.TryGetValue(btn, out hi); - if(hi == null) return; - - object objClickEvent; - EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); - if(ehl == null) { Debug.Assert(false); return; } - - ehl.RemoveHandler(objClickEvent, hi.FunctionOverride); - if(hi.FunctionOriginal != null) - ehl[objClickEvent] = hi.FunctionOriginal; - - btn.DialogResult = hi.Result; - m_dictHandlers.Remove(btn); - } - - private static void OnButtonClick(object sender, EventArgs e) - { - Button btn = (sender as Button); - if(btn == null) { Debug.Assert(false); return; } - - MwaHandlerInfo hi; - m_dictHandlers.TryGetValue(btn, out hi); - if(hi == null) { Debug.Assert(false); return; } - - Form f = hi.FormContext; - - // Set current dialog result by setting the form's private - // variable; the DialogResult property can't be used, - // because it raises close events - FieldInfo fiRes = typeof(Form).GetField("dialog_result", - BindingFlags.Instance | BindingFlags.NonPublic); - if(fiRes == null) { Debug.Assert(false); return; } - if(f != null) fiRes.SetValue(f, hi.Result); - - if(hi.FunctionOriginal != null) - hi.FunctionOriginal.Method.Invoke(hi.FunctionOriginal.Target, - new object[]{ btn, e }); - - // Raise close events, if the click event handler hasn't - // reset the dialog result - if((f != null) && (f.DialogResult == hi.Result)) - f.DialogResult = hi.Result; // Raises close events - } - - private static void SetWmClass(Form f) - { - KeePass.Native.NativeMethods.SetWmClass(f, PwDefs.UnixName, - PwDefs.ResClass); - } - - private static void OnFormHandleCreated(object sender, EventArgs e) - { - Form f = (sender as Form); - if(f == null) { Debug.Assert(false); return; } - - if(!f.IsHandleCreated) return; // Prevent infinite loop - - SetWmClass(f); - } - - /// - /// Set the value of the private shown_raised member - /// variable of a form. - /// - /// Previous shown_raised value. - internal static bool ExchangeFormShownRaised(Form f, bool bNewValue) - { - if(f == null) { Debug.Assert(false); return true; } - - try - { - FieldInfo fi = typeof(Form).GetField("shown_raised", - BindingFlags.Instance | BindingFlags.NonPublic); - if(fi == null) { Debug.Assert(false); return true; } - - bool bPrevious = (bool)fi.GetValue(f); - - fi.SetValue(f, bNewValue); - - return bPrevious; - } - catch(Exception) { Debug.Assert(false); } - - return true; - } - } -} --- a/KeePassLib/KeePassLib.csproj +++ b/KeePassLib/KeePassLib.csproj @@ -128,6 +128,8 @@ + + --- a/KeePassLib/Native/NativeLib.cs +++ b/KeePassLib/Native/NativeLib.cs @@ -23,6 +23,7 @@ using System.Windows.Forms; using System.Threading; using System.Diagnostics; +using System.Text.RegularExpressions; using KeePassLib.Utility; @@ -46,6 +47,43 @@ set { m_bAllowNative = value; } } + private static ulong? m_ouMonoVersion = null; + public static ulong MonoVersion + { + get + { + if(m_ouMonoVersion.HasValue) return m_ouMonoVersion.Value; + + ulong uVersion = 0; + try + { + Type t = Type.GetType("Mono.Runtime"); + if(t != null) + { + MethodInfo mi = t.GetMethod("GetDisplayName", + BindingFlags.NonPublic | BindingFlags.Static); + if(mi != null) + { + string strName = (mi.Invoke(null, null) as string); + if(!string.IsNullOrEmpty(strName)) + { + Match m = Regex.Match(strName, "\\d+(\\.\\d+)+"); + if(m.Success) + uVersion = StrUtil.ParseVersion(m.Value); + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + } + catch(Exception) { Debug.Assert(false); } + + m_ouMonoVersion = uVersion; + return uVersion; + } + } + /// /// Determine if the native library is installed. /// --- /dev/null +++ b/KeePassLib/Native/NativeMethods.Unix.cs @@ -0,0 +1,112 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2014 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using System.Reflection; +using System.Diagnostics; + +namespace KeePassLib.Native +{ + internal static partial class NativeMethods + { +#if (!KeePassLibSD && !KeePassRT) + [StructLayout(LayoutKind.Sequential)] + private struct XClassHint + { + public IntPtr res_name; + public IntPtr res_class; + } + + [DllImport("libX11")] + private static extern int XSetClassHint(IntPtr display, IntPtr window, IntPtr class_hints); + + private static Type m_tXplatUIX11 = null; + private static Type GetXplatUIX11Type(bool bThrowOnError) + { + if(m_tXplatUIX11 == null) + { + // CheckState is in System.Windows.Forms + string strTypeCS = typeof(CheckState).AssemblyQualifiedName; + string strTypeX11 = strTypeCS.Replace("CheckState", "XplatUIX11"); + m_tXplatUIX11 = Type.GetType(strTypeX11, bThrowOnError, true); + } + + return m_tXplatUIX11; + } + + private static Type m_tHwnd = null; + private static Type GetHwndType(bool bThrowOnError) + { + if(m_tHwnd == null) + { + // CheckState is in System.Windows.Forms + string strTypeCS = typeof(CheckState).AssemblyQualifiedName; + string strTypeHwnd = strTypeCS.Replace("CheckState", "Hwnd"); + m_tHwnd = Type.GetType(strTypeHwnd, bThrowOnError, true); + } + + return m_tHwnd; + } + + internal static void SetWmClass(Form f, string strName, string strClass) + { + if(f == null) { Debug.Assert(false); return; } + + // The following crashes under Mac OS X (SIGSEGV in native code, + // not just an exception), thus skip it when we're on Mac OS X; + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/5860588 + if(NativeLib.GetPlatformID() == PlatformID.MacOSX) return; + + try + { + Type tXplatUIX11 = GetXplatUIX11Type(true); + FieldInfo fiDisplayHandle = tXplatUIX11.GetField("DisplayHandle", + BindingFlags.NonPublic | BindingFlags.Static); + IntPtr hDisplay = (IntPtr)fiDisplayHandle.GetValue(null); + + Type tHwnd = GetHwndType(true); + MethodInfo miObjectFromHandle = tHwnd.GetMethod("ObjectFromHandle", + BindingFlags.Public | BindingFlags.Static); + object oHwnd = miObjectFromHandle.Invoke(null, new object[] { f.Handle }); + + FieldInfo fiWholeWindow = tHwnd.GetField("whole_window", + BindingFlags.NonPublic | BindingFlags.Instance); + IntPtr hWindow = (IntPtr)fiWholeWindow.GetValue(oHwnd); + + XClassHint xch = new XClassHint(); + xch.res_name = Marshal.StringToCoTaskMemAnsi(strName ?? string.Empty); + xch.res_class = Marshal.StringToCoTaskMemAnsi(strClass ?? string.Empty); + IntPtr pXch = Marshal.AllocCoTaskMem(Marshal.SizeOf(xch)); + Marshal.StructureToPtr(xch, pXch, false); + + XSetClassHint(hDisplay, hWindow, pXch); + + Marshal.FreeCoTaskMem(pXch); + Marshal.FreeCoTaskMem(xch.res_name); + Marshal.FreeCoTaskMem(xch.res_class); + } + catch(Exception) { Debug.Assert(false); } + } +#endif + } +} --- a/KeePassLib/Native/NativeMethods.cs +++ b/KeePassLib/Native/NativeMethods.cs @@ -28,7 +28,7 @@ namespace KeePassLib.Native { - internal static class NativeMethods + internal static partial class NativeMethods { internal const int MAX_PATH = 260; --- a/KeePassLib/Serialization/IOConnection.cs +++ b/KeePassLib/Serialization/IOConnection.cs @@ -22,6 +22,7 @@ using System.Text; using System.IO; using System.Net; +using System.Reflection; using System.Diagnostics; #if (!KeePassLibSD && !KeePassRT) @@ -39,7 +40,7 @@ namespace KeePassLib.Serialization { #if (!KeePassLibSD && !KeePassRT) - public sealed class IOWebClient : WebClient + internal sealed class IOWebClient : WebClient { protected override WebRequest GetWebRequest(Uri address) { @@ -50,6 +51,175 @@ } #endif + internal abstract class WrapperStream : Stream + { + private readonly Stream m_s; + protected Stream BaseStream + { + get { return m_s; } + } + + public override bool CanRead + { + get { return m_s.CanRead; } + } + + public override bool CanSeek + { + get { return m_s.CanSeek; } + } + + public override bool CanTimeout + { + get { return m_s.CanTimeout; } + } + + public override bool CanWrite + { + get { return m_s.CanWrite; } + } + + public override long Length + { + get { return m_s.Length; } + } + + public override long Position + { + get { return m_s.Position; } + set { m_s.Position = value; } + } + + public override int ReadTimeout + { + get { return m_s.ReadTimeout; } + set { m_s.ReadTimeout = value; } + } + + public override int WriteTimeout + { + get { return m_s.WriteTimeout; } + set { m_s.WriteTimeout = value; } + } + + public WrapperStream(Stream sBase) : base() + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_s = sBase; + } + + public override IAsyncResult BeginRead(byte[] buffer, int offset, + int count, AsyncCallback callback, object state) + { + return m_s.BeginRead(buffer, offset, count, callback, state); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, + int count, AsyncCallback callback, object state) + { + return BeginWrite(buffer, offset, count, callback, state); + } + + public override void Close() + { + m_s.Close(); + } + + public override int EndRead(IAsyncResult asyncResult) + { + return m_s.EndRead(asyncResult); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + m_s.EndWrite(asyncResult); + } + + public override void Flush() + { + m_s.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return m_s.Read(buffer, offset, count); + } + + public override int ReadByte() + { + return m_s.ReadByte(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return m_s.Seek(offset, origin); + } + + public override void SetLength(long value) + { + m_s.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + m_s.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + m_s.WriteByte(value); + } + } + + internal sealed class IocStream : WrapperStream + { + private readonly bool m_bWrite; // Initially opened for writing + + public IocStream(Stream sBase) : base(sBase) + { + m_bWrite = sBase.CanWrite; + } + + public override void Close() + { + base.Close(); + + if(MonoWorkarounds.IsRequired(10163) && m_bWrite) + { + try + { + Stream s = this.BaseStream; + Type t = s.GetType(); + if(t.Name == "WebConnectionStream") + { + PropertyInfo pi = t.GetProperty("Request", + BindingFlags.Instance | BindingFlags.NonPublic); + if(pi != null) + { + WebRequest wr = (pi.GetValue(s, null) as WebRequest); + if(wr != null) + IOConnection.DisposeResponse(wr.GetResponse(), false); + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + } + catch(Exception) { Debug.Assert(false); } + } + } + + public static Stream WrapIfRequired(Stream s) + { + if(s == null) { Debug.Assert(false); return null; } + + if(MonoWorkarounds.IsRequired(10163) && s.CanWrite) + return new IocStream(s); + + return s; + } + } + public static class IOConnection { #if (!KeePassLibSD && !KeePassRT) @@ -244,7 +414,8 @@ if(ioc.IsLocalFile()) return OpenReadLocal(ioc); - return CreateWebClient(ioc).OpenRead(new Uri(ioc.Path)); + return IocStream.WrapIfRequired(CreateWebClient(ioc).OpenRead( + new Uri(ioc.Path))); } #else public static Stream OpenRead(IOConnectionInfo ioc) @@ -271,15 +442,17 @@ if(ioc.IsLocalFile()) return OpenWriteLocal(ioc); Uri uri = new Uri(ioc.Path); + Stream s; // Mono does not set HttpWebRequest.Method to POST for writes, // so one needs to set the method to PUT explicitly if(NativeLib.IsUnix() && (uri.Scheme.Equals(Uri.UriSchemeHttp, StrUtil.CaseIgnoreCmp) || uri.Scheme.Equals(Uri.UriSchemeHttps, StrUtil.CaseIgnoreCmp))) - return CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put); + s = CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put); + else s = CreateWebClient(ioc).OpenWrite(uri); - return CreateWebClient(ioc).OpenWrite(uri); + return IocStream.WrapIfRequired(s); } #else public static Stream OpenWrite(IOConnectionInfo ioc) @@ -443,7 +616,7 @@ } #endif - private static void DisposeResponse(WebResponse wr, bool bGetStream) + internal static void DisposeResponse(WebResponse wr, bool bGetStream) { if(wr == null) return; --- /dev/null +++ b/KeePassLib/Utility/MonoWorkarounds.cs @@ -0,0 +1,300 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2014 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.ComponentModel; +using System.Reflection; +using System.Diagnostics; + +using KeePassLib.Native; + +namespace KeePassLib.Utility +{ + public static class MonoWorkarounds + { + private static bool? m_bReq = null; + public static bool IsRequired() + { + if(!m_bReq.HasValue) m_bReq = NativeLib.IsUnix(); + return m_bReq.Value; + } + + // 5795: + // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 + // https://sourceforge.net/p/keepass/discussion/329220/thread/d23dc88b/ + // 10163: + // https://bugzilla.xamarin.com/show_bug.cgi?id=10163 + // https://sourceforge.net/p/keepass/bugs/1117/ + // https://sourceforge.net/p/keepass/discussion/329221/thread/9422258c/ + // https://github.com/mono/mono/commit/8e67b8c2fc7cb66bff7816ebf7c1039fb8cfc43b + // https://bugzilla.xamarin.com/show_bug.cgi?id=1512 + // https://sourceforge.net/p/keepass/patches/89/ + // 12525: + // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 + // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ + // 586901: + // https://bugzilla.novell.com/show_bug.cgi?id=586901 + // 620618: + // https://bugzilla.novell.com/show_bug.cgi?id=620618 + // 649266: + // https://bugzilla.novell.com/show_bug.cgi?id=649266 + // 686017: + // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 + // 801414: + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 + // 891029: + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 + // 836428016: + // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ + // 3574233558: + // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ + public static bool IsRequired(uint uBugID) + { + if(!MonoWorkarounds.IsRequired()) return false; + + ulong v = NativeLib.MonoVersion; + if(v != 0) + { + if(uBugID == 10163) + return (v >= 0x0002000B00000000UL); // >= 2.11 + } + + return true; + } + + public static void ApplyTo(Form f) + { + if(!MonoWorkarounds.IsRequired()) return; + if(f == null) { Debug.Assert(false); return; } + +#if (!KeePassLibSD && !KeePassRT) + f.HandleCreated += MonoWorkarounds.OnFormHandleCreated; + SetWmClass(f); + + ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ApplyToControl); +#endif + } + + public static void Release(Form f) + { + if(!MonoWorkarounds.IsRequired()) return; + if(f == null) { Debug.Assert(false); return; } + +#if (!KeePassLibSD && !KeePassRT) + f.HandleCreated -= MonoWorkarounds.OnFormHandleCreated; + + ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ReleaseControl); +#endif + } + +#if (!KeePassLibSD && !KeePassRT) + private delegate void MwaControlHandler(Control c, Form fContext); + + private static void ApplyToControlsRec(Control.ControlCollection cc, + Form fContext, MwaControlHandler fn) + { + if(cc == null) { Debug.Assert(false); return; } + + foreach(Control c in cc) + { + fn(c, fContext); + ApplyToControlsRec(c.Controls, fContext, fn); + } + } + + private sealed class MwaHandlerInfo + { + private readonly Delegate m_fnOrg; // May be null + public Delegate FunctionOriginal + { + get { return m_fnOrg; } + } + + private readonly Delegate m_fnOvr; + public Delegate FunctionOverride + { + get { return m_fnOvr; } + } + + private readonly DialogResult m_dr; + public DialogResult Result + { + get { return m_dr; } + } + + private readonly Form m_fContext; + public Form FormContext + { + get { return m_fContext; } + } + + public MwaHandlerInfo(Delegate fnOrg, Delegate fnOvr, DialogResult dr, + Form fContext) + { + m_fnOrg = fnOrg; + m_fnOvr = fnOvr; + m_dr = dr; + m_fContext = fContext; + } + } + + private static void ApplyToControl(Control c, Form fContext) + { + Button btn = (c as Button); + if(btn != null) ApplyToButton(btn, fContext); + } + + private static EventHandlerList GetEventHandlers(Component c, + out object objClickEvent) + { + FieldInfo fi = typeof(Control).GetField("ClickEvent", // Mono + BindingFlags.Static | BindingFlags.NonPublic); + if(fi == null) + fi = typeof(Control).GetField("EventClick", // .NET + BindingFlags.Static | BindingFlags.NonPublic); + if(fi == null) { Debug.Assert(false); objClickEvent = null; return null; } + + objClickEvent = fi.GetValue(null); + if(objClickEvent == null) { Debug.Assert(false); return null; } + + PropertyInfo pi = typeof(Component).GetProperty("Events", + BindingFlags.Instance | BindingFlags.NonPublic); + return (pi.GetValue(c, null) as EventHandlerList); + } + + private static Dictionary m_dictHandlers = + new Dictionary(); + private static void ApplyToButton(Button btn, Form fContext) + { + DialogResult dr = btn.DialogResult; + if(dr == DialogResult.None) return; // No workaround required + + object objClickEvent; + EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); + if(ehl == null) { Debug.Assert(false); return; } + Delegate fnClick = ehl[objClickEvent]; // May be null + + EventHandler fnOvr = new EventHandler(MonoWorkarounds.OnButtonClick); + m_dictHandlers[btn] = new MwaHandlerInfo(fnClick, fnOvr, dr, fContext); + + btn.DialogResult = DialogResult.None; + if(fnClick != null) ehl.RemoveHandler(objClickEvent, fnClick); + ehl[objClickEvent] = fnOvr; + } + + private static void ReleaseControl(Control c, Form fContext) + { + Button btn = (c as Button); + if(btn != null) ReleaseButton(btn, fContext); + } + + private static void ReleaseButton(Button btn, Form fContext) + { + MwaHandlerInfo hi; + m_dictHandlers.TryGetValue(btn, out hi); + if(hi == null) return; + + object objClickEvent; + EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); + if(ehl == null) { Debug.Assert(false); return; } + + ehl.RemoveHandler(objClickEvent, hi.FunctionOverride); + if(hi.FunctionOriginal != null) + ehl[objClickEvent] = hi.FunctionOriginal; + + btn.DialogResult = hi.Result; + m_dictHandlers.Remove(btn); + } + + private static void OnButtonClick(object sender, EventArgs e) + { + Button btn = (sender as Button); + if(btn == null) { Debug.Assert(false); return; } + + MwaHandlerInfo hi; + m_dictHandlers.TryGetValue(btn, out hi); + if(hi == null) { Debug.Assert(false); return; } + + Form f = hi.FormContext; + + // Set current dialog result by setting the form's private + // variable; the DialogResult property can't be used, + // because it raises close events + FieldInfo fiRes = typeof(Form).GetField("dialog_result", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fiRes == null) { Debug.Assert(false); return; } + if(f != null) fiRes.SetValue(f, hi.Result); + + if(hi.FunctionOriginal != null) + hi.FunctionOriginal.Method.Invoke(hi.FunctionOriginal.Target, + new object[] { btn, e }); + + // Raise close events, if the click event handler hasn't + // reset the dialog result + if((f != null) && (f.DialogResult == hi.Result)) + f.DialogResult = hi.Result; // Raises close events + } + + private static void SetWmClass(Form f) + { + NativeMethods.SetWmClass(f, PwDefs.UnixName, PwDefs.ResClass); + } + + private static void OnFormHandleCreated(object sender, EventArgs e) + { + Form f = (sender as Form); + if(f == null) { Debug.Assert(false); return; } + + if(!f.IsHandleCreated) return; // Prevent infinite loop + + SetWmClass(f); + } + + /// + /// Set the value of the private shown_raised member + /// variable of a form. + /// + /// Previous shown_raised value. + internal static bool ExchangeFormShownRaised(Form f, bool bNewValue) + { + if(f == null) { Debug.Assert(false); return true; } + + try + { + FieldInfo fi = typeof(Form).GetField("shown_raised", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fi == null) { Debug.Assert(false); return true; } + + bool bPrevious = (bool)fi.GetValue(f); + + fi.SetValue(f, bNewValue); + + return bPrevious; + } + catch(Exception) { Debug.Assert(false); } + + return true; + } +#endif + } +}