1 // 2 // GtkWorkarounds.cs 3 // 4 // Authors: Jeffrey Stedfast <jeff@xamarin.com> 5 // 6 // Copyright (C) 2011 Xamarin Inc. 7 // 8 // Permission is hereby granted, free of charge, to any person obtaining a copy 9 // of this software and associated documentation files (the "Software"), to deal 10 // in the Software without restriction, including without limitation the rights 11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 // copies of the Software, and to permit persons to whom the Software is 13 // furnished to do so, subject to the following conditions: 14 // 15 // The above copyright notice and this permission notice shall be included in 16 // all copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 // THE SOFTWARE. 25 // 26 27 using System; 28 using System.Runtime.InteropServices; 29 using System.Collections.Generic; 30 using System.Reflection; 31 using System.Reflection.Emit; 32 using Gtk; 33 34 namespace Pinta.Docking 35 { 36 static class GtkWorkarounds 37 { 38 const string LIBOBJC ="/usr/lib/libobjc.dylib"; 39 const string USER32DLL = "User32.dll"; 40 41 [DllImport (LIBOBJC, EntryPoint = "sel_registerName")] sel_registerName(string selector)42 static extern IntPtr sel_registerName (string selector); 43 44 [DllImport (LIBOBJC, EntryPoint = "objc_getClass")] objc_getClass(string klass)45 static extern IntPtr objc_getClass (string klass); 46 47 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_IntPtr(IntPtr klass, IntPtr selector)48 static extern IntPtr objc_msgSend_IntPtr (IntPtr klass, IntPtr selector); 49 50 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_void_bool(IntPtr klass, IntPtr selector, bool arg)51 static extern void objc_msgSend_void_bool (IntPtr klass, IntPtr selector, bool arg); 52 53 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_bool(IntPtr klass, IntPtr selector)54 static extern bool objc_msgSend_bool (IntPtr klass, IntPtr selector); 55 56 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_NSInt32_NSInt32(IntPtr klass, IntPtr selector, int arg)57 static extern int objc_msgSend_NSInt32_NSInt32 (IntPtr klass, IntPtr selector, int arg); 58 59 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_NSInt64_NSInt64(IntPtr klass, IntPtr selector, long arg)60 static extern long objc_msgSend_NSInt64_NSInt64 (IntPtr klass, IntPtr selector, long arg); 61 62 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_NSUInt32(IntPtr klass, IntPtr selector)63 static extern uint objc_msgSend_NSUInt32 (IntPtr klass, IntPtr selector); 64 65 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")] objc_msgSend_NSUInt64(IntPtr klass, IntPtr selector)66 static extern ulong objc_msgSend_NSUInt64 (IntPtr klass, IntPtr selector); 67 68 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")] objc_msgSend_CGRect32(out CGRect32 rect, IntPtr klass, IntPtr selector)69 static extern void objc_msgSend_CGRect32 (out CGRect32 rect, IntPtr klass, IntPtr selector); 70 71 [DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")] objc_msgSend_CGRect64(out CGRect64 rect, IntPtr klass, IntPtr selector)72 static extern void objc_msgSend_CGRect64 (out CGRect64 rect, IntPtr klass, IntPtr selector); 73 74 [DllImport (PangoUtil.LIBQUARTZ)] gdk_quartz_window_get_nswindow(IntPtr window)75 static extern IntPtr gdk_quartz_window_get_nswindow (IntPtr window); 76 77 [DllImport (PangoUtil.LIBQUARTZ)] gdk_window_has_embedded_nsview_focus(IntPtr window)78 static extern bool gdk_window_has_embedded_nsview_focus (IntPtr window); 79 80 struct CGRect32 81 { 82 public float X, Y, Width, Height; 83 } 84 85 struct CGRect64 86 { 87 public double X, Y, Width, Height; 88 CGRect64Pinta.Docking.GtkWorkarounds.CGRect6489 public CGRect64 (CGRect32 rect32) 90 { 91 X = rect32.X; 92 Y = rect32.Y; 93 Width = rect32.Width; 94 Height = rect32.Height; 95 } 96 } 97 98 static IntPtr cls_NSScreen; 99 static IntPtr sel_screens, sel_objectEnumerator, sel_nextObject, sel_frame, sel_visibleFrame, 100 sel_requestUserAttention, sel_setHasShadow, sel_invalidateShadow; 101 static IntPtr sharedApp; 102 static IntPtr cls_NSEvent; 103 static IntPtr sel_modifierFlags; 104 105 const int NSCriticalRequest = 0; 106 const int NSInformationalRequest = 10; 107 108 static System.Reflection.MethodInfo glibObjectGetProp, glibObjectSetProp; 109 110 public static int GtkMinorVersion = 12, GtkMicroVersion = 0; 111 static bool oldMacKeyHacks = false; 112 GtkWorkarounds()113 static GtkWorkarounds () 114 { 115 if (Platform.IsMac) { 116 InitMac (); 117 } 118 119 var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic; 120 glibObjectSetProp = typeof (GLib.Object).GetMethod ("SetProperty", flags); 121 glibObjectGetProp = typeof (GLib.Object).GetMethod ("GetProperty", flags); 122 123 foreach (int i in new [] { 24, 22, 20, 18, 16, 14 }) { 124 if (Gtk.Global.CheckVersion (2, (uint)i, 0) == null) { 125 GtkMinorVersion = i; 126 break; 127 } 128 } 129 130 for (int i = 1; i < 20; i++) { 131 if (Gtk.Global.CheckVersion (2, (uint)GtkMinorVersion, (uint)i) == null) { 132 GtkMicroVersion = i; 133 } else { 134 break; 135 } 136 } 137 138 //opt into the fixes on GTK+ >= 2.24.8 139 if (Platform.IsMac) { 140 try { 141 gdk_quartz_set_fix_modifiers (true); 142 } catch (EntryPointNotFoundException) { 143 oldMacKeyHacks = true; 144 } 145 } 146 147 keymap.KeysChanged += delegate { 148 mappedKeys.Clear (); 149 }; 150 } 151 InitMac()152 static void InitMac () 153 { 154 cls_NSScreen = objc_getClass ("NSScreen"); 155 cls_NSEvent = objc_getClass ("NSEvent"); 156 sel_screens = sel_registerName ("screens"); 157 sel_objectEnumerator = sel_registerName ("objectEnumerator"); 158 sel_nextObject = sel_registerName ("nextObject"); 159 sel_visibleFrame = sel_registerName ("visibleFrame"); 160 sel_frame = sel_registerName ("frame"); 161 sel_requestUserAttention = sel_registerName ("requestUserAttention:"); 162 sel_modifierFlags = sel_registerName ("modifierFlags"); 163 sel_setHasShadow = sel_registerName ("setHasShadow:"); 164 sel_invalidateShadow = sel_registerName ("invalidateShadow"); 165 sharedApp = objc_msgSend_IntPtr (objc_getClass ("NSApplication"), sel_registerName ("sharedApplication")); 166 } 167 MacGetUsableMonitorGeometry(Gdk.Screen screen, int monitor)168 static Gdk.Rectangle MacGetUsableMonitorGeometry (Gdk.Screen screen, int monitor) 169 { 170 IntPtr array = objc_msgSend_IntPtr (cls_NSScreen, sel_screens); 171 IntPtr iter = objc_msgSend_IntPtr (array, sel_objectEnumerator); 172 Gdk.Rectangle ygeometry = screen.GetMonitorGeometry (monitor); 173 Gdk.Rectangle xgeometry = screen.GetMonitorGeometry (0); 174 IntPtr scrn; 175 int i = 0; 176 177 while ((scrn = objc_msgSend_IntPtr (iter, sel_nextObject)) != IntPtr.Zero && i < monitor) 178 i++; 179 180 if (scrn == IntPtr.Zero) 181 return screen.GetMonitorGeometry (monitor); 182 183 CGRect64 visible, frame; 184 185 if (IntPtr.Size == 8) { 186 objc_msgSend_CGRect64 (out visible, scrn, sel_visibleFrame); 187 objc_msgSend_CGRect64 (out frame, scrn, sel_frame); 188 } else { 189 CGRect32 visible32, frame32; 190 objc_msgSend_CGRect32 (out visible32, scrn, sel_visibleFrame); 191 objc_msgSend_CGRect32 (out frame32, scrn, sel_frame); 192 visible = new CGRect64 (visible32); 193 frame = new CGRect64 (frame32); 194 } 195 196 // Note: Frame and VisibleFrame rectangles are relative to monitor 0, but we need absolute 197 // coordinates. 198 visible.X += xgeometry.X; 199 frame.X += xgeometry.X; 200 201 // VisibleFrame.Y is the height of the Dock if it is at the bottom of the screen, so in order 202 // to get the menu height, we just figure out the difference between the visibleFrame height 203 // and the actual frame height, then subtract the Dock height. 204 // 205 // We need to swap the Y offset with the menu height because our callers expect the Y offset 206 // to be from the top of the screen, not from the bottom of the screen. 207 double x, y, width, height; 208 209 if (visible.Height < frame.Height) { 210 double dockHeight = visible.Y - frame.Y; 211 double menubarHeight = (frame.Height - visible.Height) - dockHeight; 212 213 height = frame.Height - menubarHeight - dockHeight; 214 y = ygeometry.Y + menubarHeight; 215 } else { 216 height = frame.Height; 217 y = ygeometry.Y; 218 } 219 220 // Takes care of the possibility of the Dock being positioned on the left or right edge of the screen. 221 width = System.Math.Min (visible.Width, frame.Width); 222 x = System.Math.Max (visible.X, frame.X); 223 224 return new Gdk.Rectangle ((int) x, (int) y, (int) width, (int) height); 225 } 226 MacRequestAttention(bool critical)227 static void MacRequestAttention (bool critical) 228 { 229 int kind = critical? NSCriticalRequest : NSInformationalRequest; 230 if (IntPtr.Size == 8) { 231 objc_msgSend_NSInt64_NSInt64 (sharedApp, sel_requestUserAttention, kind); 232 } else { 233 objc_msgSend_NSInt32_NSInt32 (sharedApp, sel_requestUserAttention, kind); 234 } 235 } 236 237 // Note: we can't reuse RectangleF because the layout is different... 238 [StructLayout (LayoutKind.Sequential)] 239 struct Rect { 240 public int Left; 241 public int Top; 242 public int Right; 243 public int Bottom; 244 245 public int X { get { return Left; } } 246 public int Y { get { return Top; } } 247 public int Width { get { return Right - Left; } } 248 public int Height { get { return Bottom - Top; } } 249 } 250 251 const int MonitorInfoFlagsPrimary = 0x01; 252 253 [StructLayout (LayoutKind.Sequential)] 254 unsafe struct MonitorInfo { 255 public int Size; 256 public Rect Frame; // Monitor 257 public Rect VisibleFrame; // Work 258 public int Flags; 259 public fixed byte Device[32]; 260 } 261 262 [UnmanagedFunctionPointer (CallingConvention.Winapi)] EnumMonitorsCallback(IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data)263 delegate int EnumMonitorsCallback (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data); 264 265 [DllImport (USER32DLL)] EnumDisplayMonitors(IntPtr hdc, IntPtr clip, EnumMonitorsCallback callback, IntPtr user_data)266 extern static int EnumDisplayMonitors (IntPtr hdc, IntPtr clip, EnumMonitorsCallback callback, IntPtr user_data); 267 268 [DllImport (USER32DLL)] GetMonitorInfoA(IntPtr hmonitor, ref MonitorInfo info)269 extern static int GetMonitorInfoA (IntPtr hmonitor, ref MonitorInfo info); 270 WindowsGetUsableMonitorGeometry(Gdk.Screen screen, int monitor_id)271 static Gdk.Rectangle WindowsGetUsableMonitorGeometry (Gdk.Screen screen, int monitor_id) 272 { 273 Gdk.Rectangle geometry = screen.GetMonitorGeometry (monitor_id); 274 List<MonitorInfo> screens = new List<MonitorInfo> (); 275 276 EnumDisplayMonitors (IntPtr.Zero, IntPtr.Zero, delegate (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data) { 277 var info = new MonitorInfo (); 278 279 unsafe { 280 info.Size = sizeof (MonitorInfo); 281 } 282 283 GetMonitorInfoA (hmonitor, ref info); 284 285 // In order to keep the order the same as Gtk, we need to put the primary monitor at the beginning. 286 if ((info.Flags & MonitorInfoFlagsPrimary) != 0) 287 screens.Insert (0, info); 288 else 289 screens.Add (info); 290 291 return 1; 292 }, IntPtr.Zero); 293 294 MonitorInfo monitor = screens[monitor_id]; 295 Rect visible = monitor.VisibleFrame; 296 Rect frame = monitor.Frame; 297 298 // Rebase the VisibleFrame off of Gtk's idea of this monitor's geometry (since they use different coordinate systems) 299 int x = geometry.X + (visible.Left - frame.Left); 300 int width = visible.Width; 301 302 int y = geometry.Y + (visible.Top - frame.Top); 303 int height = visible.Height; 304 305 return new Gdk.Rectangle (x, y, width, height); 306 } 307 GetUsableMonitorGeometry(this Gdk.Screen screen, int monitor)308 public static Gdk.Rectangle GetUsableMonitorGeometry (this Gdk.Screen screen, int monitor) 309 { 310 if (Platform.IsWindows) 311 return WindowsGetUsableMonitorGeometry (screen, monitor); 312 313 if (Platform.IsMac) 314 return MacGetUsableMonitorGeometry (screen, monitor); 315 316 return screen.GetMonitorGeometry (monitor); 317 } 318 RunDialogWithNotification(Gtk.Dialog dialog)319 public static int RunDialogWithNotification (Gtk.Dialog dialog) 320 { 321 if (Platform.IsMac) 322 MacRequestAttention (dialog.Modal); 323 324 return dialog.Run (); 325 } 326 PresentWindowWithNotification(this Gtk.Window window)327 public static void PresentWindowWithNotification (this Gtk.Window window) 328 { 329 window.Present (); 330 331 if (Platform.IsMac) { 332 var dialog = window as Gtk.Dialog; 333 MacRequestAttention (dialog == null? false : dialog.Modal); 334 } 335 } 336 GetProperty(this GLib.Object obj, string name)337 public static GLib.Value GetProperty (this GLib.Object obj, string name) 338 { 339 return (GLib.Value) glibObjectGetProp.Invoke (obj, new object[] { name }); 340 } 341 SetProperty(this GLib.Object obj, string name, GLib.Value value)342 public static void SetProperty (this GLib.Object obj, string name, GLib.Value value) 343 { 344 glibObjectSetProp.Invoke (obj, new object[] { name, value }); 345 } 346 TriggersContextMenu(this Gdk.EventButton evt)347 public static bool TriggersContextMenu (this Gdk.EventButton evt) 348 { 349 return evt.Type == Gdk.EventType.ButtonPress && IsContextMenuButton (evt); 350 } 351 IsContextMenuButton(this Gdk.EventButton evt)352 public static bool IsContextMenuButton (this Gdk.EventButton evt) 353 { 354 if (evt.Button == 3 && 355 (evt.State & (Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button2Mask)) == 0) 356 return true; 357 358 if (Platform.IsMac) { 359 if (!oldMacKeyHacks && 360 evt.Button == 1 && 361 (evt.State & Gdk.ModifierType.ControlMask) != 0 && 362 (evt.State & (Gdk.ModifierType.Button2Mask | Gdk.ModifierType.Button3Mask)) == 0) 363 { 364 return true; 365 } 366 } 367 368 return false; 369 } 370 GetCurrentKeyModifiers()371 public static Gdk.ModifierType GetCurrentKeyModifiers () 372 { 373 if (Platform.IsMac) { 374 Gdk.ModifierType mtype = Gdk.ModifierType.None; 375 ulong mod; 376 if (IntPtr.Size == 8) { 377 mod = objc_msgSend_NSUInt64 (cls_NSEvent, sel_modifierFlags); 378 } else { 379 mod = objc_msgSend_NSUInt32 (cls_NSEvent, sel_modifierFlags); 380 } 381 if ((mod & (1 << 17)) != 0) 382 mtype |= Gdk.ModifierType.ShiftMask; 383 if ((mod & (1 << 18)) != 0) 384 mtype |= Gdk.ModifierType.ControlMask; 385 if ((mod & (1 << 19)) != 0) 386 mtype |= Gdk.ModifierType.Mod1Mask; // Alt key 387 if ((mod & (1 << 20)) != 0) 388 mtype |= Gdk.ModifierType.Mod2Mask; // Command key 389 return mtype; 390 } 391 else { 392 Gdk.ModifierType mtype; 393 Gtk.Global.GetCurrentEventState (out mtype); 394 return mtype; 395 } 396 } 397 GetPageScrollPixelDeltas(this Gdk.EventScroll evt, double pageSizeX, double pageSizeY, out double deltaX, out double deltaY)398 public static void GetPageScrollPixelDeltas (this Gdk.EventScroll evt, double pageSizeX, double pageSizeY, 399 out double deltaX, out double deltaY) 400 { 401 if (!GetEventScrollDeltas (evt, out deltaX, out deltaY)) { 402 var direction = evt.Direction; 403 deltaX = deltaY = 0; 404 if (pageSizeY != 0 && (direction == Gdk.ScrollDirection.Down || direction == Gdk.ScrollDirection.Up)) { 405 deltaY = System.Math.Pow (pageSizeY, 2.0 / 3.0); 406 deltaX = 0.0; 407 if (direction == Gdk.ScrollDirection.Up) 408 deltaY = -deltaY; 409 } else if (pageSizeX != 0) { 410 deltaX = System.Math.Pow (pageSizeX, 2.0 / 3.0); 411 deltaY = 0.0; 412 if (direction == Gdk.ScrollDirection.Left) 413 deltaX = -deltaX; 414 } 415 } 416 } 417 AddValueClamped(this Gtk.Adjustment adj, double value)418 public static void AddValueClamped (this Gtk.Adjustment adj, double value) 419 { 420 adj.Value = System.Math.Max (adj.Lower, System.Math.Min (adj.Value + value, adj.Upper - adj.PageSize)); 421 } 422 423 [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)] gdk_event_get_scroll_deltas(IntPtr eventScroll, out double deltaX, out double deltaY)424 extern static bool gdk_event_get_scroll_deltas (IntPtr eventScroll, out double deltaX, out double deltaY); 425 static bool scrollDeltasNotSupported; 426 GetEventScrollDeltas(Gdk.EventScroll evt, out double deltaX, out double deltaY)427 public static bool GetEventScrollDeltas (Gdk.EventScroll evt, out double deltaX, out double deltaY) 428 { 429 if (!scrollDeltasNotSupported) { 430 try { 431 return gdk_event_get_scroll_deltas (evt.Handle, out deltaX, out deltaY); 432 } catch (EntryPointNotFoundException) { 433 scrollDeltasNotSupported = true; 434 } 435 } 436 deltaX = deltaY = 0; 437 return false; 438 } 439 440 /// <summary>Shows a context menu.</summary> 441 /// <param name='menu'>The menu.</param> 442 /// <param name='parent'>The parent widget.</param> 443 /// <param name='evt'>The mouse event. May be null if triggered by keyboard.</param> 444 /// <param name='caret'>The caret/selection position within the parent, if the EventButton is null.</param> ShowContextMenu(Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret)445 public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret) 446 { 447 Gtk.MenuPositionFunc posFunc = null; 448 449 if (parent != null) { 450 menu.AttachToWidget (parent, null); 451 menu.Hidden += (sender, e) => { 452 if (menu.AttachWidget != null) 453 menu.Detach (); 454 }; 455 posFunc = delegate (Gtk.Menu m, out int x, out int y, out bool pushIn) { 456 Gdk.Window window = evt != null? evt.Window : parent.GdkWindow; 457 window.GetOrigin (out x, out y); 458 var alloc = parent.Allocation; 459 if (evt != null) { 460 x += (int) evt.X; 461 y += (int) evt.Y; 462 } else if (caret.X >= alloc.X && caret.Y >= alloc.Y) { 463 x += caret.X; 464 y += caret.Y + caret.Height; 465 } else { 466 x += alloc.X; 467 y += alloc.Y; 468 } 469 Gtk.Requisition request = m.SizeRequest (); 470 var screen = parent.Screen; 471 Gdk.Rectangle geometry = GetUsableMonitorGeometry (screen, screen.GetMonitorAtPoint (x, y)); 472 473 //whether to push or flip menus that would extend offscreen 474 //FIXME: this is the correct behaviour for mac, check other platforms 475 bool flip_left = true; 476 bool flip_up = false; 477 478 if (x + request.Width > geometry.X + geometry.Width) { 479 if (flip_left) { 480 x -= request.Width; 481 } else { 482 x = geometry.X + geometry.Width - request.Width; 483 } 484 485 if (x < geometry.Left) 486 x = geometry.Left; 487 } 488 489 if (y + request.Height > geometry.Y + geometry.Height) { 490 if (flip_up) { 491 y -= request.Height; 492 } else { 493 y = geometry.Y + geometry.Height - request.Height; 494 } 495 496 if (y < geometry.Top) 497 y = geometry.Top; 498 } 499 500 pushIn = false; 501 }; 502 } 503 504 uint time; 505 uint button; 506 507 if (evt == null) { 508 time = Gtk.Global.CurrentEventTime; 509 button = 0; 510 } else { 511 time = evt.Time; 512 button = evt.Button; 513 } 514 515 //HACK: work around GTK menu issues on mac when passing button to menu.Popup 516 //some menus appear and immediately hide, and submenus don't activate 517 if (Platform.IsMac) { 518 button = 0; 519 } 520 521 menu.Popup (null, null, posFunc, button, time); 522 } 523 ShowContextMenu(Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt)524 public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt) 525 { 526 ShowContextMenu (menu, parent, evt, Gdk.Rectangle.Zero); 527 } 528 ShowContextMenu(Gtk.Menu menu, Gtk.Widget parent, Gdk.Rectangle caret)529 public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.Rectangle caret) 530 { 531 ShowContextMenu (menu, parent, null, caret); 532 } 533 534 struct MappedKeys 535 { 536 public Gdk.Key Key; 537 public Gdk.ModifierType State; 538 public KeyboardShortcut[] Shortcuts; 539 } 540 541 //introduced in GTK 2.20 542 [DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)] gdk_keymap_add_virtual_modifiers(IntPtr keymap, ref Gdk.ModifierType state)543 extern static bool gdk_keymap_add_virtual_modifiers (IntPtr keymap, ref Gdk.ModifierType state); 544 545 //Custom patch in Mono Mac w/GTK+ 2.24.8+ 546 [DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)] gdk_quartz_set_fix_modifiers(bool fix)547 extern static bool gdk_quartz_set_fix_modifiers (bool fix); 548 549 static Gdk.Keymap keymap = Gdk.Keymap.Default; 550 static Dictionary<ulong,MappedKeys> mappedKeys = new Dictionary<ulong,MappedKeys> (); 551 552 /// <summary>Map raw GTK key input to work around platform bugs and decompose accelerator keys</summary> 553 /// <param name='evt'>The raw key event</param> 554 /// <param name='key'>The composed key</param> 555 /// <param name='state'>The composed modifiers</param> 556 /// <param name='shortcuts'>All the key/modifier decompositions that can be used as accelerators</param> MapKeys(Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType state, out KeyboardShortcut[] shortcuts)557 public static void MapKeys (Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType state, 558 out KeyboardShortcut[] shortcuts) 559 { 560 //this uniquely identifies the raw key 561 ulong id; 562 unchecked { 563 id = (((ulong)(uint)evt.State) | (((ulong)evt.HardwareKeycode) << 32) | (((ulong)evt.Group) << 48)); 564 } 565 566 bool remapKey = Platform.IsWindows && evt.HardwareKeycode == 0; 567 MappedKeys mapped; 568 if (remapKey || !mappedKeys.TryGetValue (id, out mapped)) 569 mappedKeys[id] = mapped = MapKeys (evt); 570 571 shortcuts = mapped.Shortcuts; 572 state = mapped.State; 573 key = mapped.Key; 574 575 if (remapKey) { 576 key = (Gdk.Key)evt.KeyValue; 577 } 578 } 579 MapKeys(Gdk.EventKey evt)580 static MappedKeys MapKeys (Gdk.EventKey evt) 581 { 582 MappedKeys mapped; 583 Gdk.ModifierType modifier = evt.State; 584 byte grp = evt.Group; 585 586 if (GtkMinorVersion >= 20) { 587 gdk_keymap_add_virtual_modifiers (keymap.Handle, ref modifier); 588 } 589 590 //full key mapping 591 uint keyval; 592 int effectiveGroup, level; 593 Gdk.ModifierType consumedModifiers; 594 TranslateKeyboardState (evt, modifier, grp, out keyval, out effectiveGroup, 595 out level, out consumedModifiers); 596 mapped.Key = (Gdk.Key)keyval; 597 mapped.State = FixMacModifiers (evt.State & ~consumedModifiers, grp); 598 599 //decompose the key into accel combinations 600 var accelList = new List<KeyboardShortcut> (); 601 602 const Gdk.ModifierType accelMods = Gdk.ModifierType.ShiftMask | Gdk.ModifierType.Mod1Mask 603 | Gdk.ModifierType.ControlMask | Gdk.ModifierType.SuperMask |Gdk.ModifierType.MetaMask; 604 605 //all accels ignore the lock key 606 modifier &= ~Gdk.ModifierType.LockMask; 607 608 //fully decomposed 609 TranslateKeyboardState (evt, Gdk.ModifierType.None, 0, 610 out keyval, out effectiveGroup, out level, out consumedModifiers); 611 accelList.Add (new KeyboardShortcut ((Gdk.Key)keyval, FixMacModifiers (modifier, grp) & accelMods)); 612 613 //with shift composed 614 if ((modifier & Gdk.ModifierType.ShiftMask) != 0) { 615 keymap.TranslateKeyboardState (evt.HardwareKeycode, Gdk.ModifierType.ShiftMask, 0, 616 out keyval, out effectiveGroup, out level, out consumedModifiers); 617 618 if (Platform.IsWindows && evt.HardwareKeycode == 0) { 619 keyval = (ushort)evt.KeyValue; 620 } 621 622 // Prevent consumption of non-Shift modifiers (that we didn't even provide!) 623 consumedModifiers &= Gdk.ModifierType.ShiftMask; 624 625 var m = FixMacModifiers ((modifier & ~consumedModifiers), grp) & accelMods; 626 AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m)); 627 } 628 629 //with group 1 composed 630 if (grp == 1) { 631 TranslateKeyboardState (evt, modifier & ~Gdk.ModifierType.ShiftMask, 1, 632 out keyval, out effectiveGroup, out level, out consumedModifiers); 633 634 // Prevent consumption of Shift modifier (that we didn't even provide!) 635 consumedModifiers &= ~Gdk.ModifierType.ShiftMask; 636 637 var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods; 638 AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m)); 639 } 640 641 //with group 1 and shift composed 642 if (grp == 1 && (modifier & Gdk.ModifierType.ShiftMask) != 0) { 643 TranslateKeyboardState (evt, modifier, 1, 644 out keyval, out effectiveGroup, out level, out consumedModifiers); 645 var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods; 646 AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m)); 647 } 648 649 //and also allow the fully mapped key as an accel 650 AddIfNotDuplicate (accelList, new KeyboardShortcut (mapped.Key, mapped.State & accelMods)); 651 652 mapped.Shortcuts = accelList.ToArray (); 653 return mapped; 654 } 655 656 // Workaround for bug "Bug 688247 - Ctrl+Alt key not work on windows7 with bootcamp on a Mac Book Pro" 657 // Ctrl+Alt should behave like right alt key - unfortunately TranslateKeyboardState doesn't handle it. TranslateKeyboardState(Gdk.EventKey evt, Gdk.ModifierType state, int group, out uint keyval, out int effective_group, out int level, out Gdk.ModifierType consumed_modifiers)658 static void TranslateKeyboardState (Gdk.EventKey evt, Gdk.ModifierType state, int group, out uint keyval, 659 out int effective_group, out int level, out Gdk.ModifierType consumed_modifiers) 660 { 661 uint hardware_keycode = evt.HardwareKeycode; 662 663 if (Platform.IsWindows) { 664 const Gdk.ModifierType ctrlAlt = Gdk.ModifierType.ControlMask | Gdk.ModifierType.Mod1Mask; 665 if ((state & ctrlAlt) == ctrlAlt) { 666 state = (state & ~ctrlAlt) | Gdk.ModifierType.Mod2Mask; 667 group = 1; 668 } 669 // Case: Caps lock on + shift + key 670 // See: Bug 8069 - [UI Refresh] If caps lock is on, holding the shift key prevents typed characters from appearing 671 if (state.HasFlag (Gdk.ModifierType.ShiftMask)) { 672 state &= ~Gdk.ModifierType.ShiftMask; 673 } 674 } 675 676 keymap.TranslateKeyboardState (hardware_keycode, state, group, out keyval, out effective_group, 677 out level, out consumed_modifiers); 678 679 if (Platform.IsWindows && hardware_keycode == 0) { 680 keyval = evt.KeyValue; 681 } 682 } 683 FixMacModifiers(Gdk.ModifierType mod, byte grp)684 static Gdk.ModifierType FixMacModifiers (Gdk.ModifierType mod, byte grp) 685 { 686 if (!oldMacKeyHacks) 687 return mod; 688 689 // Mac GTK+ maps the command key to the Mod1 modifier, which usually means alt/ 690 // We map this instead to meta, because the Mac GTK+ has mapped the cmd key 691 // to the meta key (yay inconsistency!). IMO super would have been saner. 692 if ((mod & Gdk.ModifierType.Mod1Mask) != 0) { 693 mod ^= Gdk.ModifierType.Mod1Mask; 694 mod |= Gdk.ModifierType.MetaMask; 695 } 696 697 //some versions of GTK map opt as mod5, which converts to the virtual super modifier 698 if ((mod & (Gdk.ModifierType.Mod5Mask | Gdk.ModifierType.SuperMask)) != 0) { 699 mod ^= (Gdk.ModifierType.Mod5Mask | Gdk.ModifierType.SuperMask); 700 mod |= Gdk.ModifierType.Mod1Mask; 701 } 702 703 // When opt modifier is active, we need to decompose this to make the command appear correct for Mac. 704 // In addition, we can only inspect whether the opt/alt key is pressed by examining 705 // the key's "group", because the Mac GTK+ treats opt as a group modifier and does 706 // not expose it as an actual GDK modifier. 707 if (grp == (byte) 1) { 708 mod |= Gdk.ModifierType.Mod1Mask; 709 } 710 711 return mod; 712 } 713 714 static void AddIfNotDuplicate<T> (List<T> list, T item) where T : IEquatable<T> 715 { 716 for (int i = 0; i < list.Count; i++) { 717 if (list[i].Equals (item)) 718 return; 719 } 720 list.Add (item); 721 } 722 723 [System.Runtime.InteropServices.DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)] gdk_win32_drawable_get_handle(IntPtr drawable)724 static extern IntPtr gdk_win32_drawable_get_handle (IntPtr drawable); 725 726 enum DwmWindowAttribute 727 { 728 NcRenderingEnabled = 1, 729 NcRenderingPolicy, 730 TransitionsForceDisabled, 731 AllowNcPaint, 732 CaptionButtonBounds, 733 NonClientRtlLayout, 734 ForceIconicRepresentation, 735 Flip3DPolicy, 736 ExtendedFrameBounds, 737 HasIconicBitmap, 738 DisallowPeek, 739 ExcludedFromPeek, 740 Last, 741 } 742 743 struct Win32Rect 744 { 745 public int Left, Top, Right, Bottom; 746 Win32RectPinta.Docking.GtkWorkarounds.Win32Rect747 public Win32Rect (int left, int top, int right, int bottom) 748 { 749 this.Left = left; 750 this.Top = top; 751 this.Right = right; 752 this.Bottom = bottom; 753 } 754 } 755 756 [DllImport ("dwmapi.dll")] DwmGetWindowAttribute(IntPtr hwnd, DwmWindowAttribute attribute, out Win32Rect value, int valueSize)757 static extern int DwmGetWindowAttribute (IntPtr hwnd, DwmWindowAttribute attribute, out Win32Rect value, int valueSize); 758 759 [DllImport ("dwmapi.dll")] DwmIsCompositionEnabled(out bool enabled)760 static extern int DwmIsCompositionEnabled (out bool enabled); 761 762 [DllImport (USER32DLL)] GetWindowRect(IntPtr hwnd, out Win32Rect rect)763 static extern bool GetWindowRect (IntPtr hwnd, out Win32Rect rect); 764 SetImCursorLocation(Gtk.IMContext ctx, Gdk.Window clientWindow, Gdk.Rectangle cursor)765 public static void SetImCursorLocation (Gtk.IMContext ctx, Gdk.Window clientWindow, Gdk.Rectangle cursor) 766 { 767 // work around GTK+ Bug 663096 - Windows IME position is wrong when Aero glass is enabled 768 // https://bugzilla.gnome.org/show_bug.cgi?id=663096 769 if (Platform.IsWindows && System.Environment.OSVersion.Version.Major >= 6) { 770 bool enabled; 771 if (DwmIsCompositionEnabled (out enabled) == 0 && enabled) { 772 var hwnd = gdk_win32_drawable_get_handle (clientWindow.Toplevel.Handle); 773 Win32Rect rect; 774 // this module gets the WINVER=6 version of GetWindowRect, which returns the correct value 775 if (GetWindowRect (hwnd, out rect)) { 776 int x, y; 777 clientWindow.Toplevel.GetPosition (out x, out y); 778 cursor.X = cursor.X - x + rect.Left; 779 cursor.Y = cursor.Y - y + rect.Top - cursor.Height; 780 } 781 } 782 } 783 ctx.CursorLocation = cursor; 784 } 785 786 /// <summary>X coordinate of the pixels inside the right edge of the rectangle</summary> 787 /// <remarks>Workaround for inconsistency of Right property between GTK# versions</remarks> RightInside(this Gdk.Rectangle rect)788 public static int RightInside (this Gdk.Rectangle rect) 789 { 790 return rect.X + rect.Width - 1; 791 } 792 793 /// <summary>Y coordinate of the pixels inside the bottom edge of the rectangle</summary> 794 /// <remarks>Workaround for inconsistency of Bottom property between GTK# versions#</remarks> BottomInside(this Gdk.Rectangle rect)795 public static int BottomInside (this Gdk.Rectangle rect) 796 { 797 return rect.Y + rect.Height - 1; 798 } 799 800 /// <summary> 801 /// Shows or hides the shadow of the window rendered by the native toolkit 802 /// </summary> ShowNativeShadow(Gtk.Window window, bool show)803 public static void ShowNativeShadow (Gtk.Window window, bool show) 804 { 805 if (Platform.IsMac) { 806 var ptr = gdk_quartz_window_get_nswindow (window.GdkWindow.Handle); 807 objc_msgSend_void_bool (ptr, sel_setHasShadow, show); 808 } 809 } 810 UpdateNativeShadow(Gtk.Window window)811 public static void UpdateNativeShadow (Gtk.Window window) 812 { 813 if (!Platform.IsMac) 814 return; 815 816 var ptr = gdk_quartz_window_get_nswindow (window.GdkWindow.Handle); 817 objc_msgSend_IntPtr (ptr, sel_invalidateShadow); 818 } 819 HasNSTextFieldFocus(Gdk.Window window)820 public static bool HasNSTextFieldFocus (Gdk.Window window) 821 { 822 if (Platform.IsMac) { 823 try { 824 return gdk_window_has_embedded_nsview_focus (window.Handle); 825 } catch (Exception) { 826 return false; 827 } 828 } else { 829 return false; 830 } 831 } 832 833 [DllImport (PangoUtil.LIBGTKGLUE, CallingConvention = CallingConvention.Cdecl)] gtksharp_container_leak_fixed_marker()834 static extern void gtksharp_container_leak_fixed_marker (); 835 836 static HashSet<Type> fixedContainerTypes; 837 static Dictionary<IntPtr,ForallDelegate> forallCallbacks; 838 static bool containerLeakFixed; 839 840 // Works around BXC #3801 - Managed Container subclasses are incorrectly resurrected, then leak. 841 // It does this by registering an alternative callback for gtksharp_container_override_forall, which 842 // ignores callbacks if the wrapper no longer exists. This means that the objects no longer enter a 843 // finalized->release->dispose->re-wrap resurrection cycle. 844 // We use a dynamic method to access internal/private GTK# API in a performant way without having to track 845 // per-instance delegates. FixContainerLeak(Gtk.Container c)846 public static void FixContainerLeak (Gtk.Container c) 847 { 848 if (containerLeakFixed) { 849 return; 850 } 851 852 FixContainerLeak (c.GetType ()); 853 } 854 FixContainerLeak(Type t)855 static void FixContainerLeak (Type t) 856 { 857 if (containerLeakFixed) { 858 return; 859 } 860 861 if (fixedContainerTypes == null) { 862 try { 863 gtksharp_container_leak_fixed_marker (); 864 containerLeakFixed = true; 865 return; 866 } catch (EntryPointNotFoundException) { 867 } 868 fixedContainerTypes = new HashSet<Type>(); 869 forallCallbacks = new Dictionary<IntPtr, ForallDelegate> (); 870 } 871 872 if (!fixedContainerTypes.Add (t)) { 873 return; 874 } 875 876 //need to fix the callback for the type and all the managed supertypes 877 var lookupGType = typeof (GLib.Object).GetMethod ("LookupGType", BindingFlags.Static | BindingFlags.NonPublic); 878 do { 879 var gt = (GLib.GType) lookupGType.Invoke (null, new[] { t }); 880 var cb = CreateForallCallback (gt.Val); 881 forallCallbacks[gt.Val] = cb; 882 gtksharp_container_override_forall (gt.Val, cb); 883 t = t.BaseType; 884 } while (fixedContainerTypes.Add (t) && t.Assembly != typeof (Gtk.Container).Assembly); 885 } 886 CreateForallCallback(IntPtr gtype)887 static ForallDelegate CreateForallCallback (IntPtr gtype) 888 { 889 var dm = new DynamicMethod ( 890 "ContainerForallCallback", 891 typeof(void), 892 new Type[] { typeof(IntPtr), typeof(bool), typeof(IntPtr), typeof(IntPtr) }, 893 typeof(GtkWorkarounds).Module, 894 true); 895 896 var invokerType = typeof(Gtk.Container.CallbackInvoker); 897 898 //this was based on compiling a similar method and disassembling it 899 ILGenerator il = dm.GetILGenerator (); 900 var IL_002b = il.DefineLabel (); 901 var IL_003f = il.DefineLabel (); 902 var IL_0060 = il.DefineLabel (); 903 var label_return = il.DefineLabel (); 904 905 var loc_container = il.DeclareLocal (typeof(Gtk.Container)); 906 var loc_obj = il.DeclareLocal (typeof(object)); 907 var loc_invoker = il.DeclareLocal (invokerType); 908 var loc_ex = il.DeclareLocal (typeof(Exception)); 909 910 //check that the type is an exact match 911 // prevent stack overflow, because the callback on a more derived type will handle everything 912 il.Emit (OpCodes.Ldarg_0); 913 il.Emit (OpCodes.Call, typeof(GLib.ObjectManager).GetMethod ("gtksharp_get_type_id", BindingFlags.Static | BindingFlags.NonPublic)); 914 915 il.Emit (OpCodes.Ldc_I8, gtype.ToInt64 ()); 916 il.Emit (OpCodes.Newobj, typeof (IntPtr).GetConstructor (new Type[] { typeof (Int64) })); 917 il.Emit (OpCodes.Call, typeof (IntPtr).GetMethod ("op_Equality", BindingFlags.Static | BindingFlags.Public)); 918 il.Emit (OpCodes.Brfalse, label_return); 919 920 il.BeginExceptionBlock (); 921 il.Emit (OpCodes.Ldnull); 922 il.Emit (OpCodes.Stloc, loc_container); 923 il.Emit (OpCodes.Ldsfld, typeof (GLib.Object).GetField ("Objects", BindingFlags.Static | BindingFlags.NonPublic)); 924 il.Emit (OpCodes.Ldarg_0); 925 il.Emit (OpCodes.Box, typeof (IntPtr)); 926 il.Emit (OpCodes.Callvirt, typeof (System.Collections.Hashtable).GetProperty ("Item").GetGetMethod ()); 927 il.Emit (OpCodes.Stloc, loc_obj); 928 il.Emit (OpCodes.Ldloc, loc_obj); 929 il.Emit (OpCodes.Brfalse, IL_002b); 930 931 var tref = typeof (GLib.Object).Assembly.GetType ("GLib.ToggleRef"); 932 il.Emit (OpCodes.Ldloc, loc_obj); 933 il.Emit (OpCodes.Castclass, tref); 934 il.Emit (OpCodes.Callvirt, tref.GetProperty ("Target").GetGetMethod ()); 935 il.Emit (OpCodes.Isinst, typeof (Gtk.Container)); 936 il.Emit (OpCodes.Stloc, loc_container); 937 938 il.MarkLabel (IL_002b); 939 il.Emit (OpCodes.Ldloc, loc_container); 940 il.Emit (OpCodes.Brtrue, IL_003f); 941 942 il.Emit (OpCodes.Ldarg_0); 943 il.Emit (OpCodes.Ldarg_1); 944 il.Emit (OpCodes.Ldarg_2); 945 il.Emit (OpCodes.Ldarg_3); 946 il.Emit (OpCodes.Call, typeof (Gtk.Container).GetMethod ("gtksharp_container_base_forall", BindingFlags.Static | BindingFlags.NonPublic)); 947 il.Emit (OpCodes.Br, IL_0060); 948 949 il.MarkLabel (IL_003f); 950 il.Emit (OpCodes.Ldloca_S, 2); 951 il.Emit (OpCodes.Ldarg_2); 952 il.Emit (OpCodes.Ldarg_3); 953 il.Emit (OpCodes.Call, invokerType.GetConstructor ( 954 BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof (IntPtr), typeof (IntPtr) }, null)); 955 il.Emit (OpCodes.Ldloc, loc_container); 956 il.Emit (OpCodes.Ldarg_1); 957 il.Emit (OpCodes.Ldloc, loc_invoker); 958 il.Emit (OpCodes.Box, invokerType); 959 il.Emit (OpCodes.Ldftn, invokerType.GetMethod ("Invoke")); 960 il.Emit (OpCodes.Newobj, typeof (Gtk.Callback).GetConstructor ( 961 BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof (object), typeof (IntPtr) }, null)); 962 var forallMeth = typeof (Gtk.Container).GetMethod ("ForAll", 963 BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof (bool), typeof (Gtk.Callback) }, null); 964 il.Emit (OpCodes.Callvirt, forallMeth); 965 966 il.MarkLabel (IL_0060); 967 968 il.BeginCatchBlock (typeof (Exception)); 969 il.Emit (OpCodes.Stloc, loc_ex); 970 il.Emit (OpCodes.Ldloc, loc_ex); 971 il.Emit (OpCodes.Ldc_I4_0); 972 il.Emit (OpCodes.Call, typeof (GLib.ExceptionManager).GetMethod ("RaiseUnhandledException")); 973 il.Emit (OpCodes.Leave, label_return); 974 il.EndExceptionBlock (); 975 976 il.MarkLabel (label_return); 977 il.Emit (OpCodes.Ret); 978 979 return (ForallDelegate) dm.CreateDelegate (typeof (ForallDelegate)); 980 } 981 982 [UnmanagedFunctionPointer (CallingConvention.Cdecl)] ForallDelegate(IntPtr container, bool include_internals, IntPtr cb, IntPtr data)983 delegate void ForallDelegate (IntPtr container, bool include_internals, IntPtr cb, IntPtr data); 984 985 [DllImport(PangoUtil.LIBGTKGLUE, CallingConvention = CallingConvention.Cdecl)] gtksharp_container_override_forall(IntPtr gtype, ForallDelegate cb)986 static extern void gtksharp_container_override_forall (IntPtr gtype, ForallDelegate cb); 987 988 //public static string MarkupLinks (string text) 989 //{ 990 // if (GtkMinorVersion < 18) 991 // return text; 992 // return HighlightUrlSemanticRule.UrlRegex.Replace (text, MatchToUrl); 993 //} 994 MatchToUrl(System.Text.RegularExpressions.Match m)995 static string MatchToUrl (System.Text.RegularExpressions.Match m) 996 { 997 var s = m.ToString (); 998 return String.Format ("<a href='{0}'>{1}</a>", s, s.Replace ("_", "__")); 999 } 1000 SetLinkHandler(this Gtk.Label label, Action<string> urlHandler)1001 public static void SetLinkHandler (this Gtk.Label label, Action<string> urlHandler) 1002 { 1003 if (GtkMinorVersion >= 18) 1004 new UrlHandlerClosure (urlHandler).ConnectTo (label); 1005 } 1006 1007 //create closure manually so we can apply ConnectBefore 1008 class UrlHandlerClosure 1009 { 1010 Action<string> urlHandler; 1011 UrlHandlerClosure(Action<string> urlHandler)1012 public UrlHandlerClosure (Action<string> urlHandler) 1013 { 1014 this.urlHandler = urlHandler; 1015 } 1016 1017 [GLib.ConnectBefore] HandleLink(object sender, ActivateLinkEventArgs args)1018 void HandleLink (object sender, ActivateLinkEventArgs args) 1019 { 1020 urlHandler (args.Url); 1021 args.RetVal = true; 1022 } 1023 ConnectTo(Gtk.Label label)1024 public void ConnectTo (Gtk.Label label) 1025 { 1026 var signal = GLib.Signal.Lookup (label, "activate-link", typeof(ActivateLinkEventArgs)); 1027 signal.AddDelegate (new EventHandler<ActivateLinkEventArgs> (HandleLink)); 1028 } 1029 1030 class ActivateLinkEventArgs : GLib.SignalArgs 1031 { 1032 public string Url { get { return (string)base.Args [0]; } } 1033 } 1034 } 1035 1036 static bool canSetOverlayScrollbarPolicy = true; 1037 1038 [DllImport (PangoUtil.LIBQUARTZ)] gtk_scrolled_window_set_overlay_policy(IntPtr sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy)1039 static extern void gtk_scrolled_window_set_overlay_policy (IntPtr sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy); 1040 1041 [DllImport (PangoUtil.LIBQUARTZ)] gtk_scrolled_window_get_overlay_policy(IntPtr sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy)1042 static extern void gtk_scrolled_window_get_overlay_policy (IntPtr sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy); 1043 SetOverlayScrollbarPolicy(Gtk.ScrolledWindow sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy)1044 public static void SetOverlayScrollbarPolicy (Gtk.ScrolledWindow sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy) 1045 { 1046 if (!canSetOverlayScrollbarPolicy) { 1047 return; 1048 } 1049 try { 1050 gtk_scrolled_window_set_overlay_policy (sw.Handle, hpolicy, vpolicy); 1051 return; 1052 } catch (DllNotFoundException) { 1053 } catch (EntryPointNotFoundException) { 1054 } 1055 } 1056 GetOverlayScrollbarPolicy(Gtk.ScrolledWindow sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy)1057 public static void GetOverlayScrollbarPolicy (Gtk.ScrolledWindow sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy) 1058 { 1059 if (!canSetOverlayScrollbarPolicy) { 1060 hpolicy = vpolicy = 0; 1061 return; 1062 } 1063 try { 1064 gtk_scrolled_window_get_overlay_policy (sw.Handle, out hpolicy, out vpolicy); 1065 return; 1066 } catch (DllNotFoundException) { 1067 } catch (EntryPointNotFoundException) { 1068 } 1069 hpolicy = vpolicy = 0; 1070 canSetOverlayScrollbarPolicy = false; 1071 } 1072 1073 [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)] gtk_tree_view_get_tooltip_context(IntPtr raw, ref int x, ref int y, bool keyboard_tip, out IntPtr model, out IntPtr path, IntPtr iter)1074 static extern bool gtk_tree_view_get_tooltip_context (IntPtr raw, ref int x, ref int y, bool keyboard_tip, out IntPtr model, out IntPtr path, IntPtr iter); 1075 1076 //the GTK# version of this has 'out' instead of 'ref', preventing passing the x,y values in GetTooltipContext(this TreeView tree, ref int x, ref int y, bool keyboardTip, out TreeModel model, out TreePath path, out Gtk.TreeIter iter)1077 public static bool GetTooltipContext (this TreeView tree, ref int x, ref int y, bool keyboardTip, 1078 out TreeModel model, out TreePath path, out Gtk.TreeIter iter) 1079 { 1080 IntPtr intPtr = Marshal.AllocHGlobal (Marshal.SizeOf (typeof(TreeIter))); 1081 IntPtr handle; 1082 IntPtr intPtr2; 1083 bool result = gtk_tree_view_get_tooltip_context (tree.Handle, ref x, ref y, keyboardTip, out handle, out intPtr2, intPtr); 1084 model = TreeModelAdapter.GetObject (handle, false); 1085 path = intPtr2 == IntPtr.Zero ? null : ((TreePath)GLib.Opaque.GetOpaque (intPtr2, typeof(TreePath), false)); 1086 iter = TreeIter.New (intPtr); 1087 Marshal.FreeHGlobal (intPtr); 1088 return result; 1089 } 1090 1091 static bool supportsHiResIcons = true; 1092 1093 [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)] gtk_icon_source_set_scale(IntPtr source, double scale)1094 static extern void gtk_icon_source_set_scale (IntPtr source, double scale); 1095 1096 [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)] gtk_icon_source_set_scale_wildcarded(IntPtr source, bool setting)1097 static extern void gtk_icon_source_set_scale_wildcarded (IntPtr source, bool setting); 1098 1099 [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)] gtk_widget_get_scale_factor(IntPtr widget)1100 static extern double gtk_widget_get_scale_factor (IntPtr widget); 1101 1102 [DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)] gdk_screen_get_monitor_scale_factor(IntPtr widget, int monitor)1103 static extern double gdk_screen_get_monitor_scale_factor (IntPtr widget, int monitor); 1104 1105 [DllImport (PangoUtil.LIBGOBJECT, CallingConvention = CallingConvention.Cdecl)] g_object_get_data(IntPtr source, string name)1106 static extern IntPtr g_object_get_data (IntPtr source, string name); 1107 1108 [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)] gtk_icon_set_render_icon_scaled(IntPtr handle, IntPtr style, int direction, int state, int size, IntPtr widget, IntPtr intPtr, ref double scale)1109 static extern IntPtr gtk_icon_set_render_icon_scaled (IntPtr handle, IntPtr style, int direction, int state, int size, IntPtr widget, IntPtr intPtr, ref double scale); 1110 SetSourceScale(Gtk.IconSource source, double scale)1111 public static bool SetSourceScale (Gtk.IconSource source, double scale) 1112 { 1113 if (!supportsHiResIcons) 1114 return false; 1115 1116 try { 1117 gtk_icon_source_set_scale (source.Handle, scale); 1118 return true; 1119 } catch (DllNotFoundException) { 1120 } catch (EntryPointNotFoundException) { 1121 } 1122 supportsHiResIcons = false; 1123 return false; 1124 } 1125 SetSourceScaleWildcarded(Gtk.IconSource source, bool setting)1126 public static bool SetSourceScaleWildcarded (Gtk.IconSource source, bool setting) 1127 { 1128 if (!supportsHiResIcons) 1129 return false; 1130 1131 try { 1132 gtk_icon_source_set_scale_wildcarded (source.Handle, setting); 1133 return true; 1134 } catch (DllNotFoundException) { 1135 } catch (EntryPointNotFoundException) { 1136 } 1137 supportsHiResIcons = false; 1138 return false; 1139 } 1140 Get2xVariant(Gdk.Pixbuf px)1141 public static Gdk.Pixbuf Get2xVariant (Gdk.Pixbuf px) 1142 { 1143 if (!supportsHiResIcons) 1144 return null; 1145 1146 try { 1147 IntPtr res = g_object_get_data (px.Handle, "gdk-pixbuf-2x-variant"); 1148 if (res != IntPtr.Zero && res != px.Handle) 1149 return (Gdk.Pixbuf) GLib.Object.GetObject (res); 1150 else 1151 return null; 1152 } catch (DllNotFoundException) { 1153 } catch (EntryPointNotFoundException) { 1154 } 1155 supportsHiResIcons = false; 1156 return null; 1157 } 1158 Set2xVariant(Gdk.Pixbuf px, Gdk.Pixbuf variant2x)1159 public static void Set2xVariant (Gdk.Pixbuf px, Gdk.Pixbuf variant2x) 1160 { 1161 } 1162 GetScaleFactor(Gtk.Widget w)1163 public static double GetScaleFactor (Gtk.Widget w) 1164 { 1165 if (!supportsHiResIcons) 1166 return 1; 1167 1168 try { 1169 return gtk_widget_get_scale_factor (w.Handle); 1170 } catch (DllNotFoundException) { 1171 } catch (EntryPointNotFoundException) { 1172 } 1173 supportsHiResIcons = false; 1174 return 1; 1175 } 1176 GetScaleFactor(this Gdk.Screen screen, int monitor)1177 public static double GetScaleFactor (this Gdk.Screen screen, int monitor) 1178 { 1179 if (!supportsHiResIcons) 1180 return 1; 1181 1182 try { 1183 return gdk_screen_get_monitor_scale_factor (screen.Handle, monitor); 1184 } catch (DllNotFoundException) { 1185 } catch (EntryPointNotFoundException) { 1186 } 1187 supportsHiResIcons = false; 1188 return 1; 1189 } 1190 GetScaleFactor()1191 public static double GetScaleFactor () 1192 { 1193 return GetScaleFactor (Gdk.Screen.Default, 0); 1194 } 1195 GetPixelScale()1196 public static double GetPixelScale () 1197 { 1198 if (Platform.IsWindows) 1199 return GetScaleFactor (); 1200 else 1201 return 1d; 1202 } 1203 RenderIcon(this Gtk.IconSet iconset, Gtk.Style style, Gtk.TextDirection direction, Gtk.StateType state, Gtk.IconSize size, Gtk.Widget widget, string detail, double scale)1204 public static Gdk.Pixbuf RenderIcon (this Gtk.IconSet iconset, Gtk.Style style, Gtk.TextDirection direction, Gtk.StateType state, Gtk.IconSize size, Gtk.Widget widget, string detail, double scale) 1205 { 1206 if (scale == 1d) 1207 return iconset.RenderIcon (style, direction, state, size, widget, detail); 1208 1209 if (!supportsHiResIcons) 1210 return null; 1211 1212 try { 1213 IntPtr intPtr = GLib.Marshaller.StringToPtrGStrdup (detail); 1214 IntPtr o = gtk_icon_set_render_icon_scaled (iconset.Handle, (style != null) ? style.Handle : IntPtr.Zero, (int)direction, (int)state, (int)size, (widget != null) ? widget.Handle : IntPtr.Zero, intPtr, ref scale); 1215 Gdk.Pixbuf result = (Gdk.Pixbuf) GLib.Object.GetObject (o); 1216 GLib.Marshaller.Free (intPtr); 1217 return result; 1218 } catch (DllNotFoundException) { 1219 } catch (EntryPointNotFoundException) { 1220 } 1221 supportsHiResIcons = false; 1222 return null; 1223 } 1224 } 1225 1226 public struct KeyboardShortcut : IEquatable<KeyboardShortcut> 1227 { 1228 public static readonly KeyboardShortcut Empty = new KeyboardShortcut ((Gdk.Key) 0, (Gdk.ModifierType) 0); 1229 1230 Gdk.ModifierType modifier; 1231 Gdk.Key key; 1232 KeyboardShortcutPinta.Docking.KeyboardShortcut1233 public KeyboardShortcut (Gdk.Key key, Gdk.ModifierType modifier) 1234 { 1235 this.modifier = modifier; 1236 this.key = key; 1237 } 1238 1239 public Gdk.Key Key { 1240 get { return key; } 1241 } 1242 1243 public Gdk.ModifierType Modifier { 1244 get { return modifier; } 1245 } 1246 1247 public bool IsEmpty { 1248 get { return Key == (Gdk.Key) 0; } 1249 } 1250 EqualsPinta.Docking.KeyboardShortcut1251 public override bool Equals (object obj) 1252 { 1253 return obj is KeyboardShortcut && this.Equals ((KeyboardShortcut) obj); 1254 } 1255 GetHashCodePinta.Docking.KeyboardShortcut1256 public override int GetHashCode () 1257 { 1258 //FIXME: we're only using a few bits of mod and mostly the lower bits of key - distribute it better 1259 return (int) Key ^ (int) Modifier; 1260 } 1261 EqualsPinta.Docking.KeyboardShortcut1262 public bool Equals (KeyboardShortcut other) 1263 { 1264 return other.Key == Key && other.Modifier == Modifier; 1265 } 1266 } 1267 } 1268