Browse Source

Initial implementation of MonoMac backend

Nikita Tsukanov 8 years ago
parent
commit
33daa820eb

+ 11 - 0
src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj

@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.0;netstandard2.0;net461</TargetFrameworks>
+    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="MonoMac.NetStandard" Version="0.0.3" />
+    <PackageReference Include="Avalonia" Version="0.5.1" />
+    <PackageReference Include="Avalonia.Desktop" Version="0.5.1" />
+  </ItemGroup>
+</Project>

+ 61 - 0
src/OSX/Avalonia.MonoMac/Cursor.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Input;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+
+namespace Avalonia.MonoMac
+{
+    class Cursor : IPlatformHandle
+    {
+
+        public NSCursor Native { get; }
+
+        public IntPtr Handle => Native.Handle;
+
+        public string HandleDescriptor => "NSCursor";
+
+        public Cursor(NSCursor native)
+        {
+            Native = native;
+        }
+    }
+
+	class CursorFactoryStub : IStandardCursorFactory
+	{
+        Dictionary<StandardCursorType, NSCursor> _cache;
+        public CursorFactoryStub()
+        {
+            //TODO: Load diagonal cursors from webkit
+            //See https://stackoverflow.com/q/10733228
+            _cache = new Dictionary<StandardCursorType, NSCursor>()
+            {
+                [StandardCursorType.Arrow] = NSCursor.ArrowCursor,
+                [StandardCursorType.AppStarting] = NSCursor.ArrowCursor, //TODO
+                [StandardCursorType.BottomLeftCorner] = NSCursor.CrosshairCursor, //TODO
+                [StandardCursorType.BottomRightCorner]= NSCursor.CrosshairCursor, //TODO
+				[StandardCursorType.BottomSize] = NSCursor.ResizeDownCursor,
+                [StandardCursorType.Cross] = NSCursor.CrosshairCursor,
+                [StandardCursorType.Hand] = NSCursor.PointingHandCursor,
+                [StandardCursorType.Help] = NSCursor.ContextualMenuCursor,
+                [StandardCursorType.Ibeam] = NSCursor.IBeamCursor,
+                [StandardCursorType.LeftSide] = NSCursor.ResizeLeftCursor,
+                [StandardCursorType.No] = NSCursor.OperationNotAllowedCursor,
+                [StandardCursorType.RightSide] = NSCursor.ResizeRightCursor,
+                [StandardCursorType.SizeAll] = NSCursor.CrosshairCursor, //TODO
+                [StandardCursorType.SizeNorthSouth] = NSCursor.ResizeUpDownCursor,
+                [StandardCursorType.SizeWestEast] = NSCursor.ResizeLeftRightCursor,
+                [StandardCursorType.TopLeftCorner] = NSCursor.CrosshairCursor, //TODO
+                [StandardCursorType.TopRightCorner] = NSCursor.CrosshairCursor, //TODO
+                [StandardCursorType.TopSide] = NSCursor.ResizeUpCursor,
+                [StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor,
+                [StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO
+			};
+        }
+
+        public IPlatformHandle GetCursor(StandardCursorType cursorType)
+		{
+            return new Cursor(_cache[cursorType]);
+		}
+	}
+}

+ 74 - 0
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@@ -0,0 +1,74 @@
+using System;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+using System.Runtime.InteropServices;
+using MonoMac.CoreGraphics;
+using MonoMac.ImageIO;
+using MonoMac.Foundation;
+
+namespace Avalonia.MonoMac
+{
+    class EmulatedFramebuffer : ILockedFramebuffer
+	{
+		[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/CoreGraphics.framework/CoreGraphics")]
+		extern static IntPtr CGBitmapContextCreate (IntPtr data, UIntPtr width, UIntPtr height, UIntPtr bitsPerComponent,
+                                UIntPtr bytesPerRow, IntPtr colorSpace, uint bitmapInfo);
+
+		public EmulatedFramebuffer(NSView view)
+		{
+            //TODO: Check if this is correct
+            var factor = view.Window.UserSpaceScaleFactor;
+			var frame = view.Frame;
+            Width = (int)(frame.Width * factor);
+            Height = (int)(frame.Height * factor);
+			RowBytes = Width * 4;
+			Dpi = new Size(96, 96) * factor;
+            Format = PixelFormat.Rgba8888;
+			Address = Marshal.AllocHGlobal(Height * RowBytes);
+		}
+
+		public void Dispose()
+		{
+			if (Address == IntPtr.Zero)
+				return;
+			var nfo = (int)CGBitmapFlags.ByteOrder32Big | (int)CGImageAlphaInfo.PremultipliedLast;
+
+            using (var colorSpace = CGColorSpace.CreateDeviceRGB())
+            using (var bContext = new CGBitmapContext (Address, Width, Height, 8, Width * 4,
+				colorSpace, (CGImageAlphaInfo)nfo))
+			using (var image = bContext.ToImage())
+            using (var nscontext = NSGraphicsContext.CurrentContext)
+            using(var context = nscontext.GraphicsPort)
+			{
+                /*
+                var url = NSUrl.FromFilename("/tmp/wat.png");
+                var dest = CGImageDestination.FromUrl(url, "public.png", 1);
+                dest.AddImage(image, new NSDictionary());
+                dest.Close();
+                dest.Dispose();
+
+                var buf = new byte[Height * RowBytes];
+                Marshal.Copy(Address, buf, 0, buf.Length);
+                System.IO.File.WriteAllBytes("/tmp/wat.bin", buf);
+*/
+                // flip the image for CGContext.DrawImage
+                //context.TranslateCTM(0, Height);
+                //context.ScaleCTM(1, -1);
+
+                context.SetFillColor(255, 255, 255, 255);
+                context.FillRect(new CGRect(0, 0, Width, Height));
+
+				context.DrawImage(new CGRect(0, 0, Width, Height), image);
+			}
+			Marshal.FreeHGlobal(Address);
+			Address = IntPtr.Zero;
+		}
+
+		public IntPtr Address { get; private set; }
+		public int Width { get; }
+		public int Height { get; }
+		public int RowBytes { get; }
+		public Size Dpi { get; }
+		public PixelFormat Format { get; }
+	}
+}

+ 33 - 0
src/OSX/Avalonia.MonoMac/Helpers.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Runtime.InteropServices;
+using MonoMac.AppKit;
+using MonoMac.ObjCRuntime;
+using MonoMac.CoreGraphics;
+using MonoMac;
+namespace Avalonia.MonoMac
+{
+    static class Helpers
+    {
+        public static Point ToAvaloniaPoint(this CGPoint point) => new Point(point.X, point.Y);
+        public static CGPoint ToMonoMacPoint(this Point point) => new CGPoint(point.X, point.Y);
+        public static Size ToAvaloniaSize(this CGSize size) => new Size(size.Width, size.Height);
+        public static CGSize ToMonoMacSize(this Size size) => new CGSize(size.Width, size.Height);
+        public static Rect ToAvaloniaRect(this CGRect rect) => new Rect(rect.Left, rect.Top, rect.Width, rect.Height);
+        public static CGRect ToMonoMacRect(this Rect rect) => new CGRect(rect.X, rect.Y, rect.Width, rect.Height);
+
+		public static Point ConvertPointY(this Point pt)
+		{
+			var sw = NSScreen.Screens[0].Frame;
+			var t = Math.Max(sw.Top, sw.Bottom);
+			return pt.WithY(t - pt.Y);
+		}
+
+		public static CGPoint ConvertPointY(this CGPoint pt)
+		{
+			var sw = NSScreen.Screens[0].Frame;
+			var t = Math.Max(sw.Top, sw.Bottom);
+            return new CGPoint(pt.X, t - pt.Y);
+		}
+
+    }
+}

+ 266 - 0
src/OSX/Avalonia.MonoMac/KeyTransform.cs

@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Input;
+using MonoMac.AppKit;
+
+namespace Avalonia.MonoMac
+{
+    public static class KeyTransform
+    {
+		// See /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
+		private const int kVK_ANSI_A = 0x00;
+        private const int kVK_ANSI_S = 0x01;
+        private const int kVK_ANSI_D = 0x02;
+        private const int kVK_ANSI_F = 0x03;
+        private const int kVK_ANSI_H = 0x04;
+        private const int kVK_ANSI_G = 0x05;
+        private const int kVK_ANSI_Z = 0x06;
+        private const int kVK_ANSI_X = 0x07;
+        private const int kVK_ANSI_C = 0x08;
+        private const int kVK_ANSI_V = 0x09;
+        private const int kVK_ANSI_B = 0x0B;
+        private const int kVK_ANSI_Q = 0x0C;
+        private const int kVK_ANSI_W = 0x0D;
+        private const int kVK_ANSI_E = 0x0E;
+        private const int kVK_ANSI_R = 0x0F;
+        private const int kVK_ANSI_Y = 0x10;
+        private const int kVK_ANSI_T = 0x11;
+        private const int kVK_ANSI_1 = 0x12;
+        private const int kVK_ANSI_2 = 0x13;
+        private const int kVK_ANSI_3 = 0x14;
+        private const int kVK_ANSI_4 = 0x15;
+        private const int kVK_ANSI_6 = 0x16;
+        private const int kVK_ANSI_5 = 0x17;
+        private const int kVK_ANSI_Equal = 0x18;
+        private const int kVK_ANSI_9 = 0x19;
+        private const int kVK_ANSI_7 = 0x1A;
+        private const int kVK_ANSI_Minus = 0x1B;
+        private const int kVK_ANSI_8 = 0x1C;
+        private const int kVK_ANSI_0 = 0x1D;
+        private const int kVK_ANSI_RightBracket = 0x1E;
+        private const int kVK_ANSI_O = 0x1F;
+        private const int kVK_ANSI_U = 0x20;
+        private const int kVK_ANSI_LeftBracket = 0x21;
+        private const int kVK_ANSI_I = 0x22;
+        private const int kVK_ANSI_P = 0x23;
+        private const int kVK_ANSI_L = 0x25;
+        private const int kVK_ANSI_J = 0x26;
+        private const int kVK_ANSI_Quote = 0x27;
+        private const int kVK_ANSI_K = 0x28;
+        private const int kVK_ANSI_Semicolon = 0x29;
+        private const int kVK_ANSI_Backslash = 0x2A;
+        private const int kVK_ANSI_Comma = 0x2B;
+        private const int kVK_ANSI_Slash = 0x2C;
+        private const int kVK_ANSI_N = 0x2D;
+        private const int kVK_ANSI_M = 0x2E;
+        private const int kVK_ANSI_Period = 0x2F;
+        private const int kVK_ANSI_Grave = 0x32;
+        private const int kVK_ANSI_KeypadDecimal = 0x41;
+        private const int kVK_ANSI_KeypadMultiply = 0x43;
+        private const int kVK_ANSI_KeypadPlus = 0x45;
+        private const int kVK_ANSI_KeypadClear = 0x47;
+        private const int kVK_ANSI_KeypadDivide = 0x4B;
+        private const int kVK_ANSI_KeypadEnter = 0x4C;
+        private const int kVK_ANSI_KeypadMinus = 0x4E;
+        private const int kVK_ANSI_KeypadEquals = 0x51;
+        private const int kVK_ANSI_Keypad0 = 0x52;
+        private const int kVK_ANSI_Keypad1 = 0x53;
+        private const int kVK_ANSI_Keypad2 = 0x54;
+        private const int kVK_ANSI_Keypad3 = 0x55;
+        private const int kVK_ANSI_Keypad4 = 0x56;
+        private const int kVK_ANSI_Keypad5 = 0x57;
+        private const int kVK_ANSI_Keypad6 = 0x58;
+        private const int kVK_ANSI_Keypad7 = 0x59;
+        private const int kVK_ANSI_Keypad8 = 0x5B;
+        private const int kVK_ANSI_Keypad9 = 0x5C;
+        private const int kVK_Return = 0x24;
+        private const int kVK_Tab = 0x30;
+        private const int kVK_Space = 0x31;
+        private const int kVK_Delete = 0x33;
+        private const int kVK_Escape = 0x35;
+        private const int kVK_Command = 0x37;
+        private const int kVK_Shift = 0x38;
+        private const int kVK_CapsLock = 0x39;
+        private const int kVK_Option = 0x3A;
+        private const int kVK_Control = 0x3B;
+        private const int kVK_RightCommand = 0x36;
+        private const int kVK_RightShift = 0x3C;
+        private const int kVK_RightOption = 0x3D;
+        private const int kVK_RightControl = 0x3E;
+        private const int kVK_Function = 0x3F;
+        private const int kVK_F17 = 0x40;
+        private const int kVK_VolumeUp = 0x48;
+        private const int kVK_VolumeDown = 0x49;
+        private const int kVK_Mute = 0x4A;
+        private const int kVK_F18 = 0x4F;
+        private const int kVK_F19 = 0x50;
+        private const int kVK_F20 = 0x5A;
+        private const int kVK_F5 = 0x60;
+        private const int kVK_F6 = 0x61;
+        private const int kVK_F7 = 0x62;
+        private const int kVK_F3 = 0x63;
+        private const int kVK_F8 = 0x64;
+        private const int kVK_F9 = 0x65;
+        private const int kVK_F11 = 0x67;
+        private const int kVK_F13 = 0x69;
+        private const int kVK_F16 = 0x6A;
+        private const int kVK_F14 = 0x6B;
+        private const int kVK_F10 = 0x6D;
+        private const int kVK_F12 = 0x6F;
+        private const int kVK_F15 = 0x71;
+        private const int kVK_Help = 0x72;
+        private const int kVK_Home = 0x73;
+        private const int kVK_PageUp = 0x74;
+        private const int kVK_ForwardDelete = 0x75;
+        private const int kVK_F4 = 0x76;
+        private const int kVK_End = 0x77;
+        private const int kVK_F2 = 0x78;
+        private const int kVK_PageDown = 0x79;
+        private const int kVK_F1 = 0x7A;
+        private const int kVK_LeftArrow = 0x7B;
+        private const int kVK_RightArrow = 0x7C;
+        private const int kVK_DownArrow = 0x7D;
+        private const int kVK_UpArrow = 0x7E;
+        private const int kVK_ISO_Section = 0x0A;
+        private const int kVK_JIS_Yen = 0x5D;
+        private const int kVK_JIS_Underscore = 0x5E;
+        private const int kVK_JIS_KeypadComma = 0x5F;
+        private const int kVK_JIS_Eisu = 0x66;
+        private const int kVK_JIS_Kana = 0x68;
+
+        //TODO: Map missing keys
+        static readonly Dictionary<int, Key> Keys = new Dictionary<int, Key>
+        {
+			[kVK_ANSI_A] = Key.A,
+			[kVK_ANSI_S] = Key.S,
+			[kVK_ANSI_D] = Key.D,
+			[kVK_ANSI_F] = Key.F,
+			[kVK_ANSI_H] = Key.H,
+			[kVK_ANSI_G] = Key.G,
+			[kVK_ANSI_Z] = Key.Z,
+			[kVK_ANSI_X] = Key.X,
+			[kVK_ANSI_C] = Key.C,
+			[kVK_ANSI_V] = Key.V,
+			[kVK_ANSI_B] = Key.B,
+			[kVK_ANSI_Q] = Key.Q,
+			[kVK_ANSI_W] = Key.W,
+			[kVK_ANSI_E] = Key.E,
+			[kVK_ANSI_R] = Key.R,
+			[kVK_ANSI_Y] = Key.Y,
+			[kVK_ANSI_T] = Key.T,
+            [kVK_ANSI_1] = Key.D1,
+			[kVK_ANSI_2] = Key.D2,
+			[kVK_ANSI_3] = Key.D3,
+			[kVK_ANSI_4] = Key.D4,
+			[kVK_ANSI_6] = Key.D6,
+			[kVK_ANSI_5] = Key.D5,
+			//[kVK_ANSI_Equal] = Key.?,
+			[kVK_ANSI_9] = Key.D9,
+			[kVK_ANSI_7] = Key.D7,
+			[kVK_ANSI_Minus] = Key.OemMinus,
+			[kVK_ANSI_8] = Key.D8,
+			[kVK_ANSI_0] = Key.D0,
+            [kVK_ANSI_RightBracket] = Key.OemCloseBrackets,
+			[kVK_ANSI_O] = Key.O,
+			[kVK_ANSI_U] = Key.U,
+            [kVK_ANSI_LeftBracket] = Key.OemOpenBrackets,
+			[kVK_ANSI_I] = Key.I,
+			[kVK_ANSI_P] = Key.P,
+			[kVK_ANSI_L] = Key.L,
+			[kVK_ANSI_J] = Key.J,
+			[kVK_ANSI_Quote] = Key.OemQuotes,
+			[kVK_ANSI_K] = Key.K,
+			[kVK_ANSI_Semicolon] = Key.OemSemicolon,
+			[kVK_ANSI_Backslash] = Key.OemBackslash,
+			[kVK_ANSI_Comma] = Key.OemComma,
+			//[kVK_ANSI_Slash] = Key.?,
+			[kVK_ANSI_N] = Key.N,
+			[kVK_ANSI_M] = Key.M,
+			[kVK_ANSI_Period] = Key.OemPeriod,
+			//[kVK_ANSI_Grave] = Key.?,
+			[kVK_ANSI_KeypadDecimal] =  Key.Decimal,
+			[kVK_ANSI_KeypadMultiply] =  Key.Multiply,
+			[kVK_ANSI_KeypadPlus] =  Key.OemPlus,
+			[kVK_ANSI_KeypadClear] =  Key.Clear,
+			[kVK_ANSI_KeypadDivide] =  Key.Divide,
+			[kVK_ANSI_KeypadEnter] =  Key.Enter,
+			[kVK_ANSI_KeypadMinus] =  Key.OemMinus,
+			//[kVK_ANSI_KeypadEquals] =  Key.?,
+			[kVK_ANSI_Keypad0] =  Key.NumPad0,
+			[kVK_ANSI_Keypad1] =  Key.NumPad1,
+			[kVK_ANSI_Keypad2] =  Key.NumPad2,
+			[kVK_ANSI_Keypad3] =  Key.NumPad3,
+			[kVK_ANSI_Keypad4] =  Key.NumPad4,
+			[kVK_ANSI_Keypad5] =  Key.NumPad5,
+			[kVK_ANSI_Keypad6] =  Key.NumPad6,
+			[kVK_ANSI_Keypad7] =  Key.NumPad7,
+			[kVK_ANSI_Keypad8] =  Key.NumPad8,
+			[kVK_ANSI_Keypad9] =  Key.NumPad9,
+			[kVK_Return] = Key.Return,
+			[kVK_Tab] = Key.Tab,
+			[kVK_Space] = Key.Space,
+			[kVK_Delete] = Key.Delete,
+			[kVK_Escape] = Key.Escape,
+            [kVK_Command] = Key.LWin,
+			[kVK_Shift] = Key.LeftShift,
+			[kVK_CapsLock] = Key.CapsLock,
+            [kVK_Option] = Key.LeftAlt,
+			[kVK_Control] = Key.LeftCtrl,
+			[kVK_RightCommand] = Key.RWin,
+			[kVK_RightShift] = Key.RightShift,
+			[kVK_RightOption] = Key.RightAlt,
+			[kVK_RightControl] = Key.RightCtrl,
+			//[kVK_Function] = Key.?,
+			[kVK_F17] = Key.F17,
+			[kVK_VolumeUp] = Key.VolumeUp,
+			[kVK_VolumeDown] = Key.VolumeDown,
+			[kVK_Mute] = Key.VolumeMute,
+			[kVK_F18] = Key.F18,
+			[kVK_F19] = Key.F19,
+			[kVK_F20] = Key.F20,
+			[kVK_F5] = Key.F5,
+			[kVK_F6] = Key.F6,
+			[kVK_F7] = Key.F7,
+			[kVK_F3] = Key.F3,
+			[kVK_F8] = Key.F8,
+			[kVK_F9] = Key.F9,
+			[kVK_F11] = Key.F11,
+			[kVK_F13] = Key.F13,
+			[kVK_F16] = Key.F16,
+			[kVK_F14] = Key.F14,
+			[kVK_F10] = Key.F10,
+			[kVK_F12] = Key.F12,
+			[kVK_F15] = Key.F15,
+			[kVK_Help] = Key.Help,
+			[kVK_Home] = Key.Home,
+			[kVK_PageUp] = Key.PageUp,
+			[kVK_ForwardDelete] = Key.Delete,
+			[kVK_F4] = Key.F4,
+			[kVK_End] = Key.End,
+			[kVK_F2] = Key.F2,
+			[kVK_PageDown] = Key.PageDown,
+			[kVK_F1] = Key.F1,
+			[kVK_LeftArrow] = Key.Left,
+			[kVK_RightArrow] = Key.Right,
+			[kVK_DownArrow] = Key.Down,
+			[kVK_UpArrow] = Key.Up,
+            /*
+			[kVK_ISO_Section] = Key.?,
+			[kVK_JIS_Yen] = Key.?,
+			[kVK_JIS_Underscore] = Key.?,
+			[kVK_JIS_KeypadComma] = Key.?,
+			[kVK_JIS_Eisu] = Key.?,
+			[kVK_JIS_Kana] = Key.?
+            */
+        };
+
+
+        public static Key? TransformKeyCode(ushort code)
+        {
+            Key rv;
+            if (Keys.TryGetValue(code, out rv))
+                return rv;
+            return null;
+        }
+    }
+}

+ 77 - 0
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@@ -0,0 +1,77 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using MonoMac.Foundation;
+using MonoMac.AppKit;
+using System.Drawing;
+
+namespace Avalonia.MonoMac
+{
+    public class MonoMacPlatform : IWindowingPlatform, IPlatformSettings
+    {
+        internal static MonoMacPlatform Instance { get; private set; }
+        MouseDevice _mouseDevice = new MouseDevice();
+        KeyboardDevice _keyboardDevice = new KeyboardDevice();
+        NSApplication _app;
+        void DoInitialize()
+        {
+            AvaloniaLocator.CurrentMutable
+                .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
+                .Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
+                .Bind<IKeyboardDevice>().ToConstant(_keyboardDevice)
+                .Bind<IMouseDevice>().ToConstant(_mouseDevice)
+                .Bind<IPlatformSettings>().ToConstant(this)
+                .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
+                .Bind<IWindowingPlatform>().ToConstant(this)
+                .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
+
+            InitializeCocoaApp();
+        }
+
+        public static void Initialize()
+        {
+            Instance = new MonoMacPlatform();
+            Instance.DoInitialize();
+
+        }
+
+        void InitializeCocoaApp()
+        {
+            NSApplication.Init();
+            _app = NSApplication.SharedApplication;
+            _app.ActivationPolicy = NSApplicationActivationPolicy.Regular;
+
+        }
+
+
+		public Size DoubleClickSize => new Size(4, 4);
+		public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval);
+
+        public IWindowImpl CreateWindow() => new WindowImpl();
+
+        public IEmbeddableWindowImpl CreateEmbeddableWindow()
+        {
+            throw new PlatformNotSupportedException();
+        }
+
+        public IPopupImpl CreatePopup()
+        {
+            return new PopupImpl();
+        }
+    }
+}
+
+
+namespace Avalonia
+{
+    public static class MonoMacPlatformExtensions
+    {
+        public static AppBuilderBase<T> UseMonoMac<T>(this AppBuilderBase<T> builder) where T : AppBuilderBase<T>, new()
+        {
+            return builder.UseWindowingSubsystem(Avalonia.MonoMac.MonoMacPlatform.Initialize);
+        }
+    }
+}
+ 

+ 65 - 0
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@@ -0,0 +1,65 @@
+using System;
+using System.Threading;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+using MonoMac.CoreGraphics;
+using MonoMac.Foundation;
+
+namespace Avalonia.MonoMac
+{
+    class PlatformThreadingInterface : IPlatformThreadingInterface
+    {
+		private bool _signaled;
+		public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
+		public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
+
+		public event Action Signaled;
+
+        public IDisposable StartTimer(TimeSpan interval, Action tick)
+			=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
+
+		public void Signal()
+		{
+			lock (this)
+			{
+				if (_signaled)
+					return;
+				_signaled = true;
+			}
+            NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
+            {
+                lock(this)
+                {
+                    if (!_signaled)
+                        return;
+                    _signaled = false;
+                }
+                Signaled?.Invoke();
+            });
+		}
+
+
+
+        public void RunLoop(CancellationToken cancellationToken)
+        {
+            NSApplication.SharedApplication.ActivateIgnoringOtherApps(true);
+            //NSApplication.SharedApplication.Run();
+
+            var app = NSApplication.SharedApplication;
+            cancellationToken.Register(() =>
+            {
+                app.PostEvent(NSEvent.OtherEvent(NSEventType.ApplicationDefined, default(CGPoint),
+                                                 default(NSEventModifierMask), 0, 0, null, 0, 0, 0), true);
+            });
+            while (!cancellationToken.IsCancellationRequested)
+            {
+                var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantPast, NSRunLoop.NSDefaultRunLoopMode, true);
+                if (ev != null)
+                {
+                    app.SendEvent(ev);
+                    ev.Dispose();
+                }
+            }
+        }
+    }
+}

+ 19 - 0
src/OSX/Avalonia.MonoMac/PopupImpl.cs

@@ -0,0 +1,19 @@
+using System;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+
+namespace Avalonia.MonoMac
+{
+    class PopupImpl : WindowBaseImpl, IPopupImpl
+    {
+        public PopupImpl()
+        {
+            UpdateStyle();
+        }
+
+		protected override NSWindowStyle GetStyle()
+		{
+            return NSWindowStyle.Borderless;
+		}
+    }
+}

+ 51 - 0
src/OSX/Avalonia.MonoMac/Stubs.cs

@@ -0,0 +1,51 @@
+using System;
+using System.IO;
+using Avalonia.Input;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+
+namespace Avalonia.MonoMac
+{
+    // OSX doesn't have a concept of *window* icon. 
+    // Icons in the title bar are only shown if there is 
+    // an opened file (on disk) associated with the current window
+    // see http://stackoverflow.com/a/7038671/2231814
+    class IconLoader : IPlatformIconLoader
+    {
+        class IconStub : IWindowIconImpl
+        {
+            private readonly IBitmapImpl _bitmap;
+
+            public IconStub(IBitmapImpl bitmap)
+            {
+                _bitmap = bitmap;
+            }
+
+            public void Save(Stream outputStream)
+            {
+                _bitmap.Save(outputStream);
+            }
+        }
+
+        public IWindowIconImpl LoadIcon(string fileName)
+        {
+            return new IconStub(
+                AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().LoadBitmap(fileName));
+        }
+
+        public IWindowIconImpl LoadIcon(Stream stream)
+        {
+			return new IconStub(
+				AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().LoadBitmap(stream));
+        }
+
+        public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
+        {
+            var ms = new MemoryStream();
+            bitmap.Save(ms);
+            ms.Seek(0, SeekOrigin.Begin);
+            return LoadIcon(ms);
+        }
+    }
+
+}

+ 368 - 0
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@@ -0,0 +1,368 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using Avalonia.Controls.Platform.Surfaces;
+using MonoMac.AppKit;
+
+using MonoMac.CoreGraphics;
+using MonoMac.Foundation;
+using MonoMac.ObjCRuntime;
+
+namespace Avalonia.MonoMac
+{
+    abstract class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface
+    {
+        public TopLevelView View { get; }
+        public TopLevelImpl()
+        {
+            View = new TopLevelView(this);
+        }
+
+        [Adopts("NSTextInputClient")]
+        public class TopLevelView : NSView
+        {
+            TopLevelImpl _tl;
+            bool _isLeftPressed, _isRightPressed, _isMiddlePressed;
+            private readonly IMouseDevice _mouse;
+            private readonly IKeyboardDevice _keyboard;
+            private NSTrackingArea _area;
+            private NSCursor _cursor;
+            public TopLevelView(TopLevelImpl tl)
+            {
+                _tl = tl;
+                _mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
+                _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
+			}
+
+            public override bool ConformsToProtocol(IntPtr protocol)
+            {
+                var rv = base.ConformsToProtocol(protocol);
+                return rv;
+            }
+
+            public override void DrawRect(CGRect dirtyRect)
+            {
+                _tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect());
+            }
+
+            [Export("viewDidChangeBackingProperties:")]
+            public void ViewDidChangeBackingProperties()
+            {
+                _tl?.ScalingChanged?.Invoke(_tl.Scaling);
+            }
+
+            void UpdateCursor()
+            {
+                ResetCursorRects();
+                if (_cursor != null)
+                    AddCursorRect(Frame, _cursor);
+            }
+
+            static NSCursor ArrowCursor = NSCursor.ArrowCursor;
+            public void SetCursor(NSCursor cursor)
+            {
+                _cursor = cursor ?? ArrowCursor;
+                UpdateCursor();
+            }
+
+            public override void SetFrameSize(CGSize newSize)
+            {
+                base.SetFrameSize(newSize);
+
+                if (_area != null)
+                {
+                    RemoveTrackingArea(_area);
+                    _area.Dispose(); ;
+                }
+                _area = new NSTrackingArea(new CGRect(default(CGPoint), newSize),
+                                           NSTrackingAreaOptions.ActiveAlways |
+                                           NSTrackingAreaOptions.MouseMoved |
+                                           NSTrackingAreaOptions.EnabledDuringMouseDrag, this, null);
+                AddTrackingArea(_area);
+                UpdateCursor();
+                _tl?.Resized?.Invoke(_tl.ClientSize);
+            }
+
+            InputModifiers GetModifiers(NSEventModifierMask mod)
+            {
+                var rv = new InputModifiers();
+                if (mod.HasFlag(NSEventModifierMask.ControlKeyMask))
+                    rv |= InputModifiers.Control;
+                if (mod.HasFlag(NSEventModifierMask.ShiftKeyMask))
+                    rv |= InputModifiers.Shift;
+                if (mod.HasFlag(NSEventModifierMask.AlternateKeyMask))
+                    rv |= InputModifiers.Alt;
+                if (mod.HasFlag(NSEventModifierMask.CommandKeyMask))
+                    rv |= InputModifiers.Windows;
+
+                if (_isLeftPressed)
+                    rv |= InputModifiers.LeftMouseButton;
+                if (_isMiddlePressed)
+                    rv |= InputModifiers.MiddleMouseButton;
+                if (_isRightPressed)
+                    rv |= InputModifiers.RightMouseButton;
+                return rv;
+            }
+
+            public Point TranslateLocalPoint(Point pt) => pt.WithY(Bounds.Height - pt.Y);
+
+            Vector GetDelta(NSEvent ev)
+            {
+                var rv = new Vector(ev.ScrollingDeltaX, ev.ScrollingDeltaY);
+                //TODO: Verify if handling of HasPreciseScrollingDeltas
+                // is required (touchpad or magic-mouse is needed)
+                return rv;
+            }
+
+            uint GetTimeStamp(NSEvent ev) => (uint)(ev.Timestamp * 1000);
+
+            void MouseEvent(NSEvent ev, RawMouseEventType type)
+            {
+                BecomeFirstResponder();
+                var loc = TranslateLocalPoint(ConvertPointToView(ev.LocationInWindow, this).ToAvaloniaPoint());
+                var ts = GetTimeStamp(ev);
+                var mod = GetModifiers(ev.ModifierFlags);
+                if (type == RawMouseEventType.Wheel)
+                {
+                    var delta = GetDelta(ev);
+                    if (delta.X == 0 && delta.Y == 0)
+                        return;
+                    _tl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, ts, _tl.InputRoot, loc,
+                                                                 delta, mod));
+                }
+                else
+                    _tl.Input?.Invoke(new RawMouseEventArgs(_mouse, ts, _tl.InputRoot, type, loc, mod));
+            }
+
+            public override void MouseMoved(NSEvent theEvent)
+            {
+                MouseEvent(theEvent, RawMouseEventType.Move);
+                base.MouseMoved(theEvent);
+            }
+
+            public override void MouseDragged(NSEvent theEvent)
+            {
+                MouseEvent(theEvent, RawMouseEventType.Move);
+                base.MouseDragged(theEvent);
+            }
+
+            public override void OtherMouseDragged(NSEvent theEvent)
+            {
+                MouseEvent(theEvent, RawMouseEventType.Move);
+                base.OtherMouseDragged(theEvent);
+            }
+
+            public override void RightMouseDragged(NSEvent theEvent)
+            {
+                MouseEvent(theEvent, RawMouseEventType.Move);
+                base.RightMouseDragged(theEvent);
+            }
+
+            public NSEvent LastMouseDownEvent { get; private set; }
+
+            public override void MouseDown(NSEvent theEvent)
+            {
+                _isLeftPressed = true;
+                LastMouseDownEvent = theEvent;
+                MouseEvent(theEvent, RawMouseEventType.LeftButtonDown);
+                LastMouseDownEvent = null;
+                base.MouseDown(theEvent);
+            }
+
+            public override void RightMouseDown(NSEvent theEvent)
+            {
+                _isRightPressed = true;
+                MouseEvent(theEvent, RawMouseEventType.RightButtonDown);
+                base.RightMouseDown(theEvent);
+            }
+
+            public override void OtherMouseDown(NSEvent theEvent)
+            {
+                _isMiddlePressed = true;
+                MouseEvent(theEvent, RawMouseEventType.MiddleButtonDown);
+                base.OtherMouseDown(theEvent);
+            }
+
+            public override void MouseUp(NSEvent theEvent)
+            {
+                _isLeftPressed = false;
+                MouseEvent(theEvent, RawMouseEventType.LeftButtonUp);
+                base.MouseUp(theEvent);
+            }
+
+            public override void RightMouseUp(NSEvent theEvent)
+            {
+                _isRightPressed = false;
+                MouseEvent(theEvent, RawMouseEventType.RightButtonUp);
+                base.RightMouseUp(theEvent);
+            }
+
+            public override void OtherMouseUp(NSEvent theEvent)
+            {
+                _isMiddlePressed = false;
+                MouseEvent(theEvent, RawMouseEventType.MiddleButtonUp);
+                base.OtherMouseUp(theEvent);
+            }
+
+            public override void ScrollWheel(NSEvent theEvent)
+            {
+                MouseEvent(theEvent, RawMouseEventType.Wheel);
+                base.ScrollWheel(theEvent);
+            }
+
+            public override void MouseExited(NSEvent theEvent)
+            {
+                MouseEvent(theEvent, RawMouseEventType.LeaveWindow);
+                base.MouseExited(theEvent);
+            }
+
+            void KeyboardEvent(RawKeyEventType type, NSEvent ev)
+            {
+                var code = KeyTransform.TransformKeyCode(ev.KeyCode);
+                if (!code.HasValue)
+                    return;
+                _tl.Input?.Invoke(new RawKeyEventArgs(_keyboard, GetTimeStamp(ev),
+                     type, code.Value, GetModifiers(ev.ModifierFlags)));
+            }
+
+            public override void KeyDown(NSEvent theEvent)
+            {
+                KeyboardEvent(RawKeyEventType.KeyDown, theEvent);
+                InputContext.HandleEvent(theEvent);
+                base.KeyDown(theEvent);
+            }
+
+            public override void KeyUp(NSEvent theEvent)
+            {
+                KeyboardEvent(RawKeyEventType.KeyUp, theEvent);
+                base.KeyUp(theEvent);
+            }
+
+
+
+            #region NSTextInputClient
+
+            public override bool AcceptsFirstResponder() => true;
+
+            public bool HasMarkedText 
+            {
+                [Export("hasMarkedText")]
+                get { return false; } 
+            }
+
+			public NSRange MarkedRange
+			{
+				[Export("markedRange")]
+				get { return new NSRange(NSRange.NotFound, 0); }
+			}
+
+            public NSRange SelectedRange
+			{
+				[Export("selectedRange")]
+                get { return new NSRange(NSRange.NotFound, 0); }
+			}
+
+            [Export("setMarkedText:selectedRange:replacementRange:")]
+            public void SetMarkedText(NSString str, NSRange a1, NSRange a2)
+            {
+                
+            }
+
+            [Export("unmarkText")]
+            public void UnmarkText()
+            {
+                
+            }
+
+            public NSArray ValidAttributesForMarkedText
+            {
+                [Export("validAttributesForMarkedText")]
+                get
+                {
+                    return new NSArray();
+                }
+            }
+
+            [Export("attributedSubstringForProposedRange:actualRange:")]
+            public NSAttributedString AttributedSubstringForProposedRange(NSRange range, IntPtr wat)
+            {
+                return new NSAttributedString("");
+            }
+
+            [Export("insertText:replacementRange:")]
+            public void InsertText(NSString str, NSRange range)
+            {
+                //TODO: timestamp
+                _tl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, 0, str.ToString()));
+            }
+
+            [Export("characterIndexForPoint:")]
+            public uint CharacterIndexForPoint(CGPoint pt)
+            {
+                return 0;
+            }
+
+            [Export("firstRectForCharacterRange:actualRange:")]
+            public CGRect FirstRectForCharacterRange(NSRange range, IntPtr wat)
+            {
+                return new CGRect();
+            }
+
+			#endregion
+		}
+
+        public IInputRoot InputRoot { get; private set; }
+
+        public abstract Size ClientSize { get; }
+
+        public double Scaling 
+        {
+            get
+            {
+                if (View.Window == null)
+                    return 1;
+                return View.Window.BackingScaleFactor;
+            }
+        }
+
+        public IEnumerable<object> Surfaces => new[] { this };
+
+		#region Events
+		public Action<RawInputEventArgs> Input { get; set; }
+		public Action<Rect> Paint { get; set; }
+		public Action<Size> Resized { get; set; }
+		public Action<double> ScalingChanged { get; set; }
+		public Action Closed { get; set; }
+		#endregion
+
+		public virtual void Dispose()
+        {
+            Closed?.Invoke();
+            Closed = null;
+            View.Dispose();
+        }
+
+        public void Invalidate(Rect rect)
+        {
+            View.SetNeedsDisplayInRect(View.Frame);
+        }
+
+        public abstract Point PointToClient(Point point);
+
+        public abstract Point PointToScreen(Point point);
+
+        public void SetCursor(IPlatformHandle cursor)
+        {
+            View.SetCursor((cursor as Cursor)?.Native);
+        }
+
+        public void SetInputRoot(IInputRoot inputRoot)
+        {
+            InputRoot = inputRoot;
+        }
+
+        public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View);
+    }
+}

+ 168 - 0
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+using MonoMac.ObjCRuntime;
+
+namespace Avalonia.MonoMac
+{
+    class WindowBaseImpl : TopLevelImpl, IWindowBaseImpl
+    {
+        public CustomWindow Window { get; private set; }
+
+		public WindowBaseImpl()
+		{
+            Window = new CustomWindow(this);
+            Window.StyleMask = NSWindowStyle.Titled;
+            Window.BackingType = NSBackingStore.Buffered;
+            Window.ContentView = View;
+            Window.Delegate = CreateWindowDelegate();
+		}
+
+        public class CustomWindow : NSWindow
+        {
+            readonly WindowBaseImpl _impl;
+
+            public CustomWindow(WindowBaseImpl impl)
+            {
+                _impl = impl;
+            }
+
+            public override void BecomeKeyWindow()
+            {
+                _impl.Activated?.Invoke();
+                base.BecomeKeyWindow();
+            }
+
+            public override void ResignKeyWindow()
+            {
+                _impl.Deactivated?.Invoke();
+                base.ResignKeyWindow();
+            }
+        }
+
+        protected virtual NSWindowDelegate CreateWindowDelegate() => new WindowBaseDelegate(this);
+
+        public class WindowBaseDelegate : NSWindowDelegate
+        {
+            readonly WindowBaseImpl _impl;
+
+            public WindowBaseDelegate(WindowBaseImpl impl)
+            {
+                _impl = impl;
+            }
+
+            public override void DidMoved(global::MonoMac.Foundation.NSNotification notification)
+            {
+                _impl.PositionChanged?.Invoke(_impl.Position);
+            }
+
+            public override void WillClose(global::MonoMac.Foundation.NSNotification notification)
+            {
+                _impl.Window.Dispose();
+                _impl.Window = null;
+                _impl.Dispose();
+            }
+        }
+
+
+        public Point Position
+        {
+            get
+            {
+                var pos = Window.Frame.ToAvaloniaRect().BottomLeft.ConvertPointY();
+                //Console.WriteLine($"GET pos {pos}");
+                return pos;
+            }
+            set
+            {
+                //Console.WriteLine($"SET pos {value}");
+                Window.CascadeTopLeftFromPoint(value.ToMonoMacPoint().ConvertPointY());
+            }
+        }
+
+
+        protected virtual NSWindowStyle GetStyle()
+        {
+            return NSWindowStyle.Borderless;
+        }
+
+        protected void UpdateStyle()
+        {
+            Window.StyleMask = GetStyle();
+        }
+
+
+        IPlatformHandle IWindowBaseImpl.Handle => new PlatformHandle(Window.Handle, "NSWindow");
+        public Size MaxClientSize => NSScreen.Screens[0].Frame.ToAvaloniaRect().Size;
+		public Action<Point> PositionChanged { get; set; }
+		public Action Deactivated { get; set; }
+		public Action Activated { get; set; }
+
+		public override Size ClientSize => Window.ContentRectFor(Window.Frame).Size.ToAvaloniaSize();
+
+
+		public void Show()
+        {
+            Window.MakeKeyAndOrderFront(Window);
+        }
+
+        public void Hide()
+        {
+            Window?.OrderOut(Window);
+        }
+
+
+        public void BeginMoveDrag()
+        {
+            var ev = View.LastMouseDownEvent;
+            if (ev == null)
+                return;
+            var handle = Selector.GetHandle("performWindowDragWithEvent:");
+            Messaging.void_objc_msgSend_IntPtr(Window.Handle, handle, ev.Handle);
+        }
+
+        public void BeginResizeDrag(WindowEdge edge)
+        {
+            //TODO: Intercept mouse events and implement resize drag manually
+        }
+
+        public void Activate()
+        {
+            Window.MakeKeyWindow();
+        }
+
+        public void Resize(Size clientSize)
+        {
+            var pos = Position;
+            Window.SetContentSize(clientSize.ToMonoMacSize());
+            Position = pos;
+        }
+
+        public override Point PointToClient(Point point)
+        {
+            var cocoaScreenPoint = point.ToMonoMacPoint().ConvertPointY();
+            var cocoaViewPoint = Window.ConvertScreenToBase(cocoaScreenPoint).ToAvaloniaPoint();
+            return View.TranslateLocalPoint(cocoaViewPoint);
+        }
+
+        public override Point PointToScreen(Point point)
+        {
+            var cocoaViewPoint = View.TranslateLocalPoint(point).ToMonoMacPoint();
+            var cocoaScreenPoint = Window.ConvertBaseToScreen(cocoaViewPoint);
+            return cocoaScreenPoint.ConvertPointY().ToAvaloniaPoint();
+        }
+
+
+
+        public override void Dispose()
+        {
+            Window?.Close();
+            Window?.Dispose();
+			base.Dispose();
+        }
+    }
+}

+ 92 - 0
src/OSX/Avalonia.MonoMac/WindowImpl.cs

@@ -0,0 +1,92 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+
+namespace Avalonia.MonoMac
+{
+    class WindowImpl : WindowBaseImpl, IWindowImpl
+    {
+        bool _decorated = true;
+        public WindowImpl()
+        {
+            UpdateStyle();
+        }
+
+        public WindowState WindowState
+        {
+            get
+            {
+                if (Window.IsMiniaturized)
+                    return WindowState.Minimized;
+                return WindowState.Normal;
+
+            }
+            set
+            {
+                if (value == WindowState.Maximized)
+                {
+                    if (Window.IsMiniaturized)
+                        Window.Deminiaturize(Window);
+                    if (!Window.IsZoomed)
+                        Window.PerformZoom(Window);
+                }
+                else if (value.HasFlag(WindowState.Minimized))
+                    Window.Miniaturize(Window);
+                else
+                {
+                    if (Window.IsMiniaturized)
+                        Window.Deminiaturize(Window);
+                    if (Window.IsZoomed)
+                        Window.IsZoomed = false;
+                }
+            }
+        }
+
+        public void SetIcon(IWindowIconImpl icon)
+        {
+			//No-OP, see http://stackoverflow.com/a/7038671/2231814
+		}
+
+        protected override NSWindowStyle GetStyle()
+        {
+            if (_decorated)
+                return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled;
+            return NSWindowStyle.Borderless;
+        }
+
+        public void SetSystemDecorations(bool enabled)
+        {
+            _decorated = true;
+            UpdateStyle();
+        }
+
+        public void SetTitle(string title)
+        {
+            Window.Title = title;
+        }
+
+        class ModalDisposable : IDisposable
+        {
+            readonly WindowImpl impl;
+
+            public ModalDisposable(WindowImpl impl)
+            {
+                this.impl = impl;
+            }
+
+            public void Dispose()
+            {
+                impl.Window.OrderOut(impl.Window);
+            }
+        }
+
+        public IDisposable ShowDialog()
+        {
+            //TODO: Investigate how to return immediately. 
+            // May be add some magic to our run loop or something
+            NSApplication.SharedApplication.RunModalForWindow(Window);
+            return new ModalDisposable(this);
+        }
+    }
+}