Files
modernkeepass/ModernKeePassLib/Utility/MonoWorkarounds.cs

325 lines
9.9 KiB
C#

/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2014 Dominik Reichl <dominik.reichl@t-online.de>
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 ModernKeePassLib.Native;
namespace ModernKeePassLib.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<object, MwaHandlerInfo> m_dictHandlers =
new Dictionary<object, MwaHandlerInfo>();
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);
}
/// <summary>
/// Set the value of the private <c>shown_raised</c> member
/// variable of a form.
/// </summary>
/// <returns>Previous <c>shown_raised</c> value.</returns>
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
}
}