context: //
  // GtkWorkarounds.cs
  //
  // Authors: Jeffrey Stedfast <jeff@xamarin.com>
  //
  // Copyright (C) 2011 Xamarin Inc.
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  // THE SOFTWARE.
  //
  
  using System;
  using System.Drawing;
  using System.Runtime.InteropServices;
  using System.Collections.Generic;
  using System.Reflection;
  using System.Reflection.Emit;
  using Docking.Tools;
  
  namespace Docking.Helper
  {
  	public static class GtkWorkarounds
  	{
  		const string LIBOBJC ="/usr/lib/libobjc.dylib";
  		
  		[DllImport (LIBOBJC, EntryPoint = "sel_registerName")]
  		static extern IntPtr sel_registerName (string selector);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_getClass")]
  		static extern IntPtr objc_getClass (string klass);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
  		static extern IntPtr objc_msgSend_IntPtr (IntPtr klass, IntPtr selector);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
  		static extern void objc_msgSend_void_bool (IntPtr klass, IntPtr selector, bool arg);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
  		static extern bool objc_msgSend_bool (IntPtr klass, IntPtr selector);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
  		static extern bool objc_msgSend_int_int (IntPtr klass, IntPtr selector, int arg);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
  		static extern int objc_msgSend_int (IntPtr klass, IntPtr selector);
  		
  		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")]
  		static extern void objc_msgSend_RectangleF (out RectangleF rect, IntPtr klass, IntPtr selector);
  		
  		static IntPtr cls_NSScreen;
  		static IntPtr sel_screens, sel_objectEnumerator, sel_nextObject, sel_frame, sel_visibleFrame,
  			sel_requestUserAttention;
  		static IntPtr sharedApp;
  		static IntPtr cls_NSEvent;
  		static IntPtr sel_modifierFlags;
  		
  		const int NSCriticalRequest = 0;
  		const int NSInformationalRequest = 10;
  		
  		static System.Reflection.MethodInfo glibObjectGetProp, glibObjectSetProp;
  		
  		public static int GtkMinorVersion = 12, GtkMicroVersion = 0;
  		static bool oldMacKeyHacks = false;
  		
  		static GtkWorkarounds ()
  		{
  			if (Platform.IsMacOS) {
  				InitMac ();
  			}
  			
  			var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
  			glibObjectSetProp = typeof (GLib.Object).GetMethod ("SetProperty", flags);
  			glibObjectGetProp = typeof (GLib.Object).GetMethod ("GetProperty", flags);
  			
  			foreach (int i in new [] { 24, 22, 20, 18, 16, 14 }) {
  				if (Gtk.Global.CheckVersion (2, (uint)i, 0) == null) {
  					GtkMinorVersion = i;
  					break;
  				}
  			}
  			
  			for (int i = 1; i < 20; i++) {
  				if (Gtk.Global.CheckVersion (2, (uint)GtkMinorVersion, (uint)i) == null) {
  					GtkMicroVersion = i;
  				} else {
  					break;
  				}
  			}
  			
  			//opt into the fixes on GTK+ >= 2.24.8
  #if false
  			if (Platform.IsMac) {
  				try {
  					gdk_quartz_set_fix_modifiers (true);
  				} catch (EntryPointNotFoundException) {
  					oldMacKeyHacks = true;
  				}
  			}
  #endif	
  			keymap.KeysChanged += delegate {
  				mappedKeys.Clear ();
  			};
  		}
  
  		static void InitMac ()
  		{
  			cls_NSScreen = objc_getClass ("NSScreen");
  			cls_NSEvent = objc_getClass ("NSEvent");
  			sel_screens = sel_registerName ("screens");
  			sel_objectEnumerator = sel_registerName ("objectEnumerator");
  			sel_nextObject = sel_registerName ("nextObject");
  			sel_visibleFrame = sel_registerName ("visibleFrame");
  			sel_frame = sel_registerName ("frame");
  			sel_requestUserAttention = sel_registerName ("requestUserAttention:");
  			sel_modifierFlags = sel_registerName ("modifierFlags");
  			sharedApp = objc_msgSend_IntPtr (objc_getClass ("NSApplication"), sel_registerName ("sharedApplication"));
  		}
  		
  		static Gdk.Rectangle MacGetUsableMonitorGeometry (Gdk.Screen screen, int monitor)
  		{
  			IntPtr array = objc_msgSend_IntPtr (cls_NSScreen, sel_screens);
  			IntPtr iter = objc_msgSend_IntPtr (array, sel_objectEnumerator);
  			Gdk.Rectangle ygeometry = screen.GetMonitorGeometry (monitor);
  			Gdk.Rectangle xgeometry = screen.GetMonitorGeometry (0);
  			RectangleF visible, frame;
  			IntPtr scrn;
  			int i = 0;
  			
  			while ((scrn = objc_msgSend_IntPtr (iter, sel_nextObject)) != IntPtr.Zero && i < monitor)
  				i++;
  			
  			if (scrn == IntPtr.Zero)
  				return screen.GetMonitorGeometry (monitor);
  			
  			objc_msgSend_RectangleF (out visible, scrn, sel_visibleFrame);
  			objc_msgSend_RectangleF (out frame, scrn, sel_frame);
  			
  			// Note: Frame and VisibleFrame rectangles are relative to monitor 0, but we need absolute
  			// coordinates.
  			visible.X += xgeometry.X;
  			frame.X += xgeometry.X;
  			
  			// VisibleFrame.Y is the height of the Dock if it is at the bottom of the screen, so in order
  			// to get the menu height, we just figure out the difference between the visibleFrame height
  			// and the actual frame height, then subtract the Dock height.
  			//
  			// We need to swap the Y offset with the menu height because our callers expect the Y offset
  			// to be from the top of the screen, not from the bottom of the screen.
  			float x, y, width, height;
  			
  			if (visible.Height < frame.Height) {
  				float dockHeight = visible.Y - frame.Y;
  				float menubarHeight = (frame.Height - visible.Height) - dockHeight;
  				
  				height = frame.Height - menubarHeight - dockHeight;
  				y = ygeometry.Y + menubarHeight;
  			} else {
  				height = frame.Height;
  				y = ygeometry.Y;
  			}
  			
  			// Takes care of the possibility of the Dock being positioned on the left or right edge of the screen.
  			width = System.Math.Min (visible.Width, frame.Width);
  			x = System.Math.Max (visible.X, frame.X);
  			
  			return new Gdk.Rectangle ((int) x, (int) y, (int) width, (int) height);
  		}
  		
  		static void MacRequestAttention (bool critical)
  		{
  			int kind = critical?  NSCriticalRequest : NSInformationalRequest;
  			objc_msgSend_int_int (sharedApp, sel_requestUserAttention, kind);
  		}
  
  		// Note: we can't reuse RectangleF because the layout is different...
  		[StructLayout (LayoutKind.Sequential)]
  		struct Rect {
  			public int Left;
  			public int Top;
  			public int Right;
  			public int Bottom;
  
  			public int X { get { return Left; } }
  			public int Y { get { return Top; } }
  			public int Width { get { return Right - Left; } }
  			public int Height { get { return Bottom - Top; } }
  		}
  
  		const int MonitorInfoFlagsPrimary = 0x01;
  
  		[StructLayout (LayoutKind.Sequential)]
  		unsafe struct MonitorInfo {
  			public int Size;
  			public Rect Frame;         // Monitor
  			public Rect VisibleFrame;  // Work
  			public int Flags;
  			public fixed byte Device[32];
  		}
  
  		[UnmanagedFunctionPointer (CallingConvention.Winapi)]
  		delegate int EnumMonitorsCallback (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data);
  
  		[DllImport ("User32.dll")]
  		extern static int EnumDisplayMonitors (IntPtr hdc, IntPtr clip, EnumMonitorsCallback callback, IntPtr user_data);
  
  		[DllImport ("User32.dll")]
  		extern static int GetMonitorInfoA (IntPtr hmonitor, ref MonitorInfo info);
  
  		static Gdk.Rectangle WindowsGetUsableMonitorGeometry (Gdk.Screen screen, int monitor_id)
  		{
  			Gdk.Rectangle geometry = screen.GetMonitorGeometry (monitor_id);
  			List<MonitorInfo> screens = new List<MonitorInfo> ();
  
  			EnumDisplayMonitors (IntPtr.Zero, IntPtr.Zero, delegate (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data) {
  				var info = new MonitorInfo ();
  
  				unsafe {
  					info.Size = sizeof (MonitorInfo);
  				}
  
  				GetMonitorInfoA (hmonitor, ref info);
  
  				// In order to keep the order the same as Gtk, we need to put the primary monitor at the beginning.
  				if ((info.Flags & MonitorInfoFlagsPrimary) != 0)
  					screens.Insert (0, info);
  				else
  					screens.Add (info);
  
  				return 1;
  			}, IntPtr.Zero);
  
  			MonitorInfo monitor = screens[monitor_id];
  			Rect visible = monitor.VisibleFrame;
  			Rect frame = monitor.Frame;
  
  			// Rebase the VisibleFrame off of Gtk's idea of this monitor's geometry (since they use different coordinate systems)
  			int x = geometry.X + (visible.Left - frame.Left);
  			int width = visible.Width;
  
  			int y = geometry.Y + (visible.Top - frame.Top);
  			int height = visible.Height;
  
  			return new Gdk.Rectangle (x, y, width, height);
  		}
  		
  		public static Gdk.Rectangle GetUsableMonitorGeometry (this Gdk.Screen screen, int monitor)
  		{
  			if (Platform.IsWindows)
  				return WindowsGetUsableMonitorGeometry (screen, monitor);
  
  			if (Platform.IsMacOS)
  				return MacGetUsableMonitorGeometry (screen, monitor);
  			
  			return screen.GetMonitorGeometry (monitor);
  		}
  		
  		public static int RunDialogWithNotification (Gtk.Dialog dialog)
  		{
  			if (Platform.IsMacOS)
  				MacRequestAttention (dialog.Modal);
  			
  			return dialog.Run ();
  		}
  		
  		public static void PresentWindowWithNotification (this Gtk.Window window)
  		{
  			window.Present ();
  			
  			if (Platform.IsMacOS) {
  				var dialog = window as Gtk.Dialog;
  				MacRequestAttention (dialog == null? false : dialog.Modal);
  			}
  		}
  		
  		public static GLib.Value GetProperty (this GLib.Object obj, string name)
  		{
  			return (GLib.Value) glibObjectGetProp.Invoke (obj, new object[] { name });
  		}
  		
  		public static void SetProperty (this GLib.Object obj, string name, GLib.Value value)
  		{
  			glibObjectSetProp.Invoke (obj, new object[] { name, value });
  		}
  		
  		public static bool TriggersContextMenu (this Gdk.EventButton evt)
  		{
  			return evt.Type == Gdk.EventType.ButtonPress && IsContextMenuButton (evt);
  		}
  		
  		public static bool IsContextMenuButton (this Gdk.EventButton evt)
  		{
  			if (evt.Button == Mouse.RIGHT_MOUSE_BUTTON &&
  					(evt.State & (Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button2Mask)) == 0)
  				return true;
  			
  			if (Platform.IsMacOS) {
  				if (!oldMacKeyHacks &&
  					evt.Button == Mouse.LEFT_MOUSE_BUTTON &&
  					(evt.State & Gdk.ModifierType.ControlMask) != 0 &&
  					(evt.State & (Gdk.ModifierType.Button2Mask | Gdk.ModifierType.Button3Mask)) == 0)
  				{
  					return true;
  				}
  			}
  			
  			return false;
  		}
  
  		public static Gdk.ModifierType GetCurrentKeyModifiers ()
  		{
  			if (Platform.IsMacOS) {
  				Gdk.ModifierType mtype = Gdk.ModifierType.None;
  				int mod = objc_msgSend_int (cls_NSEvent, sel_modifierFlags);
  				if ((mod & (1 << 17)) != 0)
  					mtype |= Gdk.ModifierType.ShiftMask;
  				if ((mod & (1 << 18)) != 0)
  					mtype |= Gdk.ModifierType.ControlMask;
  				if ((mod & (1 << 19)) != 0)
  					mtype |= Gdk.ModifierType.Mod1Mask; // Alt key
  				if ((mod & (1 << 20)) != 0)
  					mtype |= Gdk.ModifierType.Mod2Mask; // Command key
  				return mtype;
  			}
  			else {
  				Gdk.ModifierType mtype;
  				Gtk.Global.GetCurrentEventState (out mtype);
  				return mtype;
  			}
  		}
  		
  		public static void GetPageScrollPixelDeltas (this Gdk.EventScroll evt, double pageSizeX, double pageSizeY,
  			out double deltaX, out double deltaY)
  		{
  			if (!GetEventScrollDeltas (evt, out deltaX, out deltaY)) {
  				var direction = evt.Direction;
  				deltaX = deltaY = 0;
  				if (pageSizeY != 0 && (direction == Gdk.ScrollDirection.Down || direction == Gdk.ScrollDirection.Up)) {
  					deltaY = System.Math.Pow (pageSizeY, 2.0 / 3.0);
  					deltaX = 0.0;
  					if (direction == Gdk.ScrollDirection.Up)
  						deltaY = -deltaY;
  				} else if (pageSizeX != 0) {
  					deltaX = System.Math.Pow (pageSizeX, 2.0 / 3.0);
  					deltaY = 0.0;
  					if (direction == Gdk.ScrollDirection.Left)
  						deltaX = -deltaX;
  				}
  			}
  		}
  		
  		public static void AddValueClamped (this Gtk.Adjustment adj, double value)
  		{
  			adj.Value = System.Math.Max (adj.Lower, System.Math.Min (adj.Value + value, adj.Upper - adj.PageSize));
  		}
  		
  		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
  		extern static bool gdk_event_get_scroll_deltas (IntPtr eventScroll, out double deltaX, out double deltaY);
  		static bool scrollDeltasNotSupported;
  		
  		public static bool GetEventScrollDeltas (Gdk.EventScroll evt, out double deltaX, out double deltaY)
  		{
  			if (!scrollDeltasNotSupported) {
  				try {
  					return gdk_event_get_scroll_deltas (evt.Handle, out deltaX, out deltaY);
  				} catch (EntryPointNotFoundException) {
  					scrollDeltasNotSupported = true;
  				}
  			}
  			deltaX = deltaY = 0;
  			return false;
  		}
  		
  		/// <summary>Shows a context menu.</summary>
  		/// <param name='menu'>The menu.</param>
  		/// <param name='parent'>The parent widget.</param>
  		/// <param name='evt'>The mouse event. May be null if triggered by keyboard.</param>
  		/// <param name='caret'>The caret/selection position within the parent, if the EventButton is null.</param>
  		public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret)
  		{
  			Gtk.MenuPositionFunc posFunc = null;
  			
  			// NOTE: we don't gtk_menu_attach_to_widget to the parent because it seems to cause issues.
  			// The expanders in other treeviews in MD stop working when we detach it, and detaching is necessary
  			// to prevent memory leaks.
  			// Attaching means menu moves when parent is moved and is destroyed when parent is destroyed. Neither is
  			// particularly important for us.
  			// See https://bugzilla.xamarin.com/show_bug.cgi?id=4388
  			if (parent != null) {
  				posFunc = delegate (Gtk.Menu m, out int x, out int y, out bool pushIn) {
  					Gdk.Window window = evt != null? evt.Window : parent.GdkWindow;
  					window.GetOrigin (out x, out y);
  					var alloc = parent.Allocation;
  					if (evt != null) {
  						x += (int) evt.X;
  						y += (int) evt.Y;
  					} else if (caret.X >= alloc.X && caret.Y >= alloc.Y) {
  						x += caret.X;
  						y += caret.Y + caret.Height;
  					} else {
  						x += alloc.X;
  						y += alloc.Y;
  					}
  					Gtk.Requisition request = m.SizeRequest ();
  					var screen = parent.Screen;
  					Gdk.Rectangle geometry = GetUsableMonitorGeometry (screen, screen.GetMonitorAtPoint (x, y));
  					
  					//whether to push or flip menus that would extend offscreen
  					//FIXME: this is the correct behaviour for mac, check other platforms
  					bool flip_left = true;
  					bool flip_up   = false;
  					
  					if (x + request.Width > geometry.X + geometry.Width) {
  						if (flip_left) {
  							x -= request.Width;
  						} else {
  							x = geometry.X + geometry.Width - request.Width;
  						}
  						
  						if (x < geometry.Left)
  							x = geometry.Left;
  					}
  					
  					if (y + request.Height > geometry.Y + geometry.Height) {
  						if (flip_up) {
  							y -= request.Height;
  						} else {
  							y = geometry.Y + geometry.Height - request.Height;
  						}
  						
  						if (y < geometry.Top)
  							y = geometry.Top;
  					}
  					
  					pushIn = false;
  				};
  			}
  			
  			uint time;
  			uint button;
  			
  			if (evt == null) {
  				time = Gtk.Global.CurrentEventTime;
  				button = 0;
  			} else {
  				time = evt.Time;
  				button = evt.Button;
  			}
  			
  			//HACK: work around GTK menu issues on mac when passing button to menu.Popup
  			//some menus appear and immediately hide, and submenus don't activate
  			if (Platform.IsMacOS) {
  				button = 0;
  			}
  			
  			menu.Popup (null, null, posFunc, button, time);
  		}
  		
  		public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt)
  		{
  			ShowContextMenu (menu, parent, evt, Gdk.Rectangle.Zero);
  		}
  		
  		public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.Rectangle caret)
  		{
  			ShowContextMenu (menu, parent, null, caret);
  		}
  		
  		struct MappedKeys
  		{
  			public Gdk.Key Key;
  			public Gdk.ModifierType State;
  			public KeyboardShortcut[] Shortcuts;
  		}
  		
  		//introduced in GTK 2.20
  		[DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
  		extern static bool gdk_keymap_add_virtual_modifiers (IntPtr keymap, ref Gdk.ModifierType state);
  		
  		//Custom patch in Mono Mac w/GTK+ 2.24.8+
  		[DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
  		extern static bool gdk_quartz_set_fix_modifiers (bool fix);
  		
  		static Gdk.Keymap keymap = Gdk.Keymap.Default;
  		static Dictionary<ulong,MappedKeys> mappedKeys = new Dictionary<ulong,MappedKeys> ();
  		
  		/// <summary>Map raw GTK key input to work around platform bugs and decompose accelerator keys</summary>
  		/// <param name='evt'>The raw key event</param>
  		/// <param name='key'>The composed key</param>
  		/// <param name='mod'>The composed modifiers</param>
  		/// <param name='shortcuts'>All the key/modifier decompositions that can be used as accelerators</param>
  		public static void MapKeys (Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType state,
  		                            out KeyboardShortcut[] shortcuts)
  		{
  			//this uniquely identifies the raw key
  			ulong id;
  			unchecked {
  				id = (((ulong)(uint)evt.State) | (((ulong)evt.HardwareKeycode) << 32) | (((ulong)evt.Group) << 48));
  			}
  			
  			MappedKeys mapped;
  			if (!mappedKeys.TryGetValue (id, out mapped))
  				mappedKeys[id] = mapped = MapKeys (evt);
  			
  			shortcuts = mapped.Shortcuts;
  			state = mapped.State;
  			key = mapped.Key;
  		}
  		
  		static MappedKeys MapKeys (Gdk.EventKey evt)
  		{
  			MappedKeys mapped;
  			ushort keycode = evt.HardwareKeycode;
  			Gdk.ModifierType modifier = evt.State;
  			byte grp = evt.Group;
  			
  			if (GtkMinorVersion >= 20) {
  				gdk_keymap_add_virtual_modifiers (keymap.Handle, ref modifier);
  			}
  			
  			//full key mapping
  			uint keyval;
  			int effectiveGroup, level;
  			Gdk.ModifierType consumedModifiers;
  			TranslateKeyboardState (keycode, modifier, grp, out keyval, out effectiveGroup,
  				out level, out consumedModifiers);
  			mapped.Key = (Gdk.Key)keyval;
  			mapped.State = FixMacModifiers (evt.State & ~consumedModifiers, grp);
  			
  			//decompose the key into accel combinations
  			var accelList = new List<KeyboardShortcut> ();
  			
  			const Gdk.ModifierType accelMods = Gdk.ModifierType.ShiftMask | Gdk.ModifierType.Mod1Mask
  				| Gdk.ModifierType.ControlMask | Gdk.ModifierType.SuperMask |Gdk.ModifierType.MetaMask;
  			
  			//all accels ignore the lock key
  			modifier &= ~Gdk.ModifierType.LockMask;
  			
  			//fully decomposed
  			TranslateKeyboardState (evt.HardwareKeycode, Gdk.ModifierType.None, 0,
  				out keyval, out effectiveGroup, out level, out consumedModifiers);
  			accelList.Add (new KeyboardShortcut ((Gdk.Key)keyval, FixMacModifiers (modifier, grp) & accelMods));
  			
  			//with shift composed
  			if ((modifier & Gdk.ModifierType.ShiftMask) != 0) {
  				keymap.TranslateKeyboardState (evt.HardwareKeycode, Gdk.ModifierType.ShiftMask, 0,
  					out keyval, out effectiveGroup, out level, out consumedModifiers);
  				
  				// Prevent consumption of non-Shift modifiers (that we didn't even provide!)
  				consumedModifiers &= Gdk.ModifierType.ShiftMask;
  				
  				var m = FixMacModifiers ((modifier & ~consumedModifiers), grp) & accelMods;
  				AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
  			}
  			
  			//with group 1 composed
  			if (grp == 1) {
  				TranslateKeyboardState (evt.HardwareKeycode, modifier & ~Gdk.ModifierType.ShiftMask, 1,
  					out keyval, out effectiveGroup, out level, out consumedModifiers);
  				
  				// Prevent consumption of Shift modifier (that we didn't even provide!)
  				consumedModifiers &= ~Gdk.ModifierType.ShiftMask;
  				
  				var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods;
  				AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
  			}
  			
  			//with group 1 and shift composed
  			if (grp == 1 && (modifier & Gdk.ModifierType.ShiftMask) != 0) {
  				TranslateKeyboardState (evt.HardwareKeycode, modifier, 1,
  					out keyval, out effectiveGroup, out level, out consumedModifiers);
  				var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods;
  				AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
  			}
  			
  			//and also allow the fully mapped key as an accel
  			AddIfNotDuplicate (accelList, new KeyboardShortcut (mapped.Key, mapped.State & accelMods));
  			
  			mapped.Shortcuts = accelList.ToArray ();
  			return mapped;
  		}
  		
  		// Workaround for bug "Bug 688247 - Ctrl+Alt key not work on windows7 with bootcamp on a Mac Book Pro"
  		// Ctrl+Alt should behave like right alt key - unfortunately TranslateKeyboardState doesn't handle it. 
  		static void TranslateKeyboardState (uint hardware_keycode, Gdk.ModifierType state, int group, out uint keyval,
  			out int effective_group, out int level, out Gdk.ModifierType consumed_modifiers)
  		{
  			if (Platform.IsWindows) {
  				const Gdk.ModifierType ctrlAlt = Gdk.ModifierType.ControlMask | Gdk.ModifierType.Mod1Mask;
  
gt: 				if ((state & ctrlAlt) == ctrlAlt) {