/* 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 ModernKeePassLibPCL.Native; namespace ModernKeePassLibPCL.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; } // 1245: // Key events not raised while Alt is down, and nav keys out of order. // https://sourceforge.net/p/keepass/bugs/1245/ // 1254: // NumericUpDown bug: text is drawn below up/down buttons. // https://sourceforge.net/p/keepass/bugs/1254/ // 5795: // Text in input field is incomplete. // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 // https://sourceforge.net/p/keepass/discussion/329220/thread/d23dc88b/ // 10163: // WebRequest GetResponse call missing, breaks WebDAV due to no PUT. // 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: // PictureBox not rendered when bitmap height >= control height. // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ // 586901: // RichTextBox doesn't handle Unicode string correctly. // https://bugzilla.novell.com/show_bug.cgi?id=586901 // 620618: // ListView column headers not drawn. // https://bugzilla.novell.com/show_bug.cgi?id=620618 // 649266: // Calling Control.Hide doesn't remove the application from taskbar. // https://bugzilla.novell.com/show_bug.cgi?id=649266 // 686017: // Minimum sizes must be enforced. // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 // 801414: // Mono recreates the main window incorrectly. // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 // 891029: // Increase tab control height, otherwise Mono throws exceptions. // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 // 836428016: // ListView group header selection unsupported. // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ // 3574233558: // Problems with minimizing windows, no content rendered. // 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 static void ApplyToControl(Control c, Form fContext) { Button btn = (c as Button); if(btn != null) ApplyToButton(btn, fContext); NumericUpDown nc = (c as NumericUpDown); if((nc != null) && MonoWorkarounds.IsRequired(1254)) { if(nc.TextAlign == HorizontalAlignment.Right) nc.TextAlign = HorizontalAlignment.Left; } } 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 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 } }