diff --git a/Source/Examples/OpenTK/Test/TestResolutionChanges.cs b/Source/Examples/OpenTK/Test/TestResolutionChanges.cs index 314a34a3..740f60f4 100644 --- a/Source/Examples/OpenTK/Test/TestResolutionChanges.cs +++ b/Source/Examples/OpenTK/Test/TestResolutionChanges.cs @@ -23,16 +23,35 @@ namespace Examples.Tests DisplayDevice dev = DisplayDevice.GetDisplay(DisplayIndex.First + i); if (dev != null) { - Trace.WriteLine(dev.ToString()); - MessageBox.Show(dev.ToString()); + Print(dev.ToString()); + dev.ChangeResolution(dev.SelectResolution(640, 480, 32, 60.0f)); Thread.Sleep(1000); - MessageBox.Show(dev.ToString()); + Print(dev.ToString()); + dev.RestoreResolution(); Thread.Sleep(1000); - MessageBox.Show(dev.ToString()); + Print(dev.ToString()); } } } + + static void Print(string msg) + { + Trace.WriteLine(msg); + + // Also display a MessageBox when running on a platform + // with WinForms support. + try + { + if (Configuration.RunningOnWindows || Configuration.RunningOnX11 || Configuration.RunningOnMacOS) + { + MessageBox.Show(msg); + } + } + catch + { + } + } } } diff --git a/Source/OpenTK/Configuration.cs b/Source/OpenTK/Configuration.cs index 2e04c773..1b29eaef 100644 --- a/Source/OpenTK/Configuration.cs +++ b/Source/OpenTK/Configuration.cs @@ -95,7 +95,7 @@ namespace OpenTK #region public static bool RunningOnLinux - /// Gets a System.Boolean indicating whether OpenTK is running on an X11 platform. + /// Gets a System.Boolean indicating whether OpenTK is running on the Linux kernel. public static bool RunningOnLinux { get { return runningOnLinux; } } #endregion diff --git a/Source/OpenTK/DisplayDevice.cs b/Source/OpenTK/DisplayDevice.cs index 4e8c7a3d..00789473 100644 --- a/Source/OpenTK/DisplayDevice.cs +++ b/Source/OpenTK/DisplayDevice.cs @@ -351,6 +351,26 @@ namespace OpenTK #endregion + #region FromPoint + + internal static DisplayDevice FromPoint(int x, int y) + { + for (DisplayIndex i = DisplayIndex.First; i < DisplayIndex.Sixth; i++) + { + DisplayDevice display = DisplayDevice.GetDisplay(i); + if (display != null) + { + if (display.Bounds.Contains(x, y)) + { + return display; + } + } + } + return null; + } + + #endregion + #endregion #region --- Private Methods --- diff --git a/Source/OpenTK/Graphics/GraphicsBindingsBase.cs b/Source/OpenTK/Graphics/GraphicsBindingsBase.cs index 237b8783..79cd42b6 100644 --- a/Source/OpenTK/Graphics/GraphicsBindingsBase.cs +++ b/Source/OpenTK/Graphics/GraphicsBindingsBase.cs @@ -26,6 +26,7 @@ #endregion using System; +using System.Diagnostics; using System.Runtime.InteropServices; namespace OpenTK.Graphics @@ -84,6 +85,8 @@ namespace OpenTK.Graphics // validation necessary.) internal override void LoadEntryPoints() { + Debug.Print("Loading entry points for {0}", GetType().FullName); + IGraphicsContext context = GraphicsContext.CurrentContext; if (context == null) throw new GraphicsContextMissingException(); diff --git a/Source/OpenTK/Input/MouseState.cs b/Source/OpenTK/Input/MouseState.cs index 3a74a0f3..9c316e01 100644 --- a/Source/OpenTK/Input/MouseState.cs +++ b/Source/OpenTK/Input/MouseState.cs @@ -38,7 +38,8 @@ namespace OpenTK.Input { #region Fields - int x, y; + internal const int MaxButtons = 16; // we are storing in an ushort + Vector2 position; MouseScroll scroll; ushort buttons; bool is_connected; @@ -101,7 +102,7 @@ namespace OpenTK.Input } /// - /// Gets a instance, + /// Gets a instance, /// representing the current state of the mouse scroll wheel. /// public MouseScroll Scroll @@ -114,8 +115,8 @@ namespace OpenTK.Input /// public int X { - get { return x; } - internal set { x = value; } + get { return (int)Math.Round(position.X); } + internal set { position.X = value; } } /// @@ -123,8 +124,8 @@ namespace OpenTK.Input /// public int Y { - get { return y; } - internal set { y = value; } + get { return (int)Math.Round(position.Y); } + internal set { position.Y = value; } } /// @@ -272,6 +273,12 @@ namespace OpenTK.Input #region Internal Members + internal Vector2 Position + { + get { return position; } + set { position = value; } + } + internal bool ReadBit(int offset) { ValidateOffset(offset); diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 718d8540..61e11bc4 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -343,9 +343,6 @@ Code - - Code - Code @@ -806,6 +803,27 @@ + + + + + + + Code + + + + + + + + + + + + + + @@ -836,4 +854,8 @@ + + + + \ No newline at end of file diff --git a/Source/OpenTK/Platform/DeviceCollection.cs b/Source/OpenTK/Platform/DeviceCollection.cs new file mode 100644 index 00000000..0c70f356 --- /dev/null +++ b/Source/OpenTK/Platform/DeviceCollection.cs @@ -0,0 +1,151 @@ +#region License +// +// DeviceCollection.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace OpenTK.Platform +{ + // Holds a collection of hardware devices with an associated id. + // Note: 'id' refers to a unique hardware-specific device identifier. + // Note: 'index' refers to the offset of the device in the Devices array. + // Indices are allocated sequentially as devices are added to the system. + // If a device is removed, its index will be reused for the next device + // that is added. + class DeviceCollection : IEnumerable + { + readonly Dictionary Map = new Dictionary(); + readonly List Devices = new List(); + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return Devices.GetEnumerator(); + } + + #endregion + + #region IEnumerable implementation + + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + #region Public Members + + public void Add(int id, T device) + { + if (!Map.ContainsKey(id)) + { + int index = GetIndex(); + Map.Add(id, index); + } + + Devices[Map[id]] = device; + } + + public void Remove(int id) + { + if (!TryRemove(id)) + { + Debug.Print("Invalid DeviceCollection<{0}> id: {1}", typeof(T).FullName, id); + } + } + + public bool TryRemove(int id) + { + if (!Map.ContainsKey(id)) + { + return false; + } + + Devices[Map[id]] = default(T); + Map.Remove(id); + return true; + } + + public T FromIndex(int index) + { + if (index >= 0 && index < Devices.Count) + { + return Devices[index]; + } + else + { + return default(T); + } + } + + public T FromHardwareId(int id) + { + if (Map.ContainsKey(id)) + { + return FromIndex(Map[id]); + } + else + { + return default(T); + } + } + + public int Count + { + get { return Map.Count; } + } + + #endregion + + #region Private Members + + // Return the index of the first empty slot in Devices. + // If no empty slot exists, append a new one and return + // that index. + int GetIndex() + { + for (int i = 0; i < Devices.Count; i++) + { + if (Devices[i] == null) + { + return i; + } + } + + Devices.Add(default(T)); + return Devices.Count - 1; + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Egl/Egl.cs b/Source/OpenTK/Platform/Egl/Egl.cs index f271f5db..9d539da0 100644 --- a/Source/OpenTK/Platform/Egl/Egl.cs +++ b/Source/OpenTK/Platform/Egl/Egl.cs @@ -41,6 +41,13 @@ namespace OpenTK.Platform.Egl using EGLSurface = IntPtr; using EGLClientBuffer = IntPtr; + enum RenderApi + { + ES = Egl.OPENGL_ES_API, + GL = Egl.OPENGL_API, + VG = Egl.OPENVG_API + } + [Flags] enum RenderableFlags { @@ -50,6 +57,24 @@ namespace OpenTK.Platform.Egl VG = Egl.OPENVG_BIT, } + enum ErrorCode + { + SUCCESS = 12288, + NOT_INITIALIZED = 12289, + BAD_ACCESS = 12290, + BAD_ALLOC = 12291, + BAD_ATTRIBUTE = 12292, + BAD_CONFIG = 12293, + BAD_CONTEXT = 12294, + BAD_CURRENT_SURFACE = 12295, + BAD_DISPLAY = 12296, + BAD_MATCH = 12297, + BAD_NATIVE_PIXMAP = 12298, + BAD_NATIVE_WINDOW = 12299, + BAD_PARAMETER = 12300, + BAD_SURFACE = 12301, + } + static partial class Egl { public const int VERSION_1_0 = 1; @@ -60,20 +85,6 @@ namespace OpenTK.Platform.Egl public const int FALSE = 0; public const int TRUE = 1; public const int DONT_CARE = -1; - public const int SUCCESS = 12288; - public const int NOT_INITIALIZED = 12289; - public const int BAD_ACCESS = 12290; - public const int BAD_ALLOC = 12291; - public const int BAD_ATTRIBUTE = 12292; - public const int BAD_CONFIG = 12293; - public const int BAD_CONTEXT = 12294; - public const int BAD_CURRENT_SURFACE = 12295; - public const int BAD_DISPLAY = 12296; - public const int BAD_MATCH = 12297; - public const int BAD_NATIVE_PIXMAP = 12298; - public const int BAD_NATIVE_WINDOW = 12299; - public const int BAD_PARAMETER = 12300; - public const int BAD_SURFACE = 12301; public const int CONTEXT_LOST = 12302; public const int BUFFER_SIZE = 12320; public const int ALPHA_SIZE = 12321; @@ -178,7 +189,7 @@ namespace OpenTK.Platform.Egl public const int ALPHA_FORMAT_PRE = VG_ALPHA_FORMAT_PRE; [DllImportAttribute("libEGL.dll", EntryPoint = "eglGetError")] - public static extern int GetError(); + public static extern ErrorCode GetError(); [DllImportAttribute("libEGL.dll", EntryPoint = "eglGetDisplay")] public static extern EGLDisplay GetDisplay(EGLNativeDisplayType display_id); @@ -225,7 +236,7 @@ namespace OpenTK.Platform.Egl [DllImportAttribute("libEGL.dll", EntryPoint = "eglBindAPI")] [return: MarshalAsAttribute(UnmanagedType.I1)] - public static extern bool BindAPI(int api); + public static extern bool BindAPI(RenderApi api); [DllImportAttribute("libEGL.dll", EntryPoint = "eglQueryAPI")] public static extern int QueryAPI(); diff --git a/Source/OpenTK/Platform/Egl/EglContext.cs b/Source/OpenTK/Platform/Egl/EglContext.cs index 37398f00..575174ff 100644 --- a/Source/OpenTK/Platform/Egl/EglContext.cs +++ b/Source/OpenTK/Platform/Egl/EglContext.cs @@ -35,9 +35,9 @@ namespace OpenTK.Platform.Egl { #region Fields - readonly RenderableFlags Renderable; + protected readonly RenderableFlags Renderable; + protected EglWindowInfo WindowInfo; - EglWindowInfo WindowInfo; IntPtr HandleAsEGLContext { get { return Handle.Handle; } set { Handle = new ContextHandle(value); } } int swap_interval = 1; // Default interval is defined as 1 in EGL. @@ -60,7 +60,20 @@ namespace OpenTK.Platform.Egl // Select an EGLConfig that matches the desired mode. We cannot use the 'mode' // parameter directly, since it may have originated on a different system (e.g. GLX) // and it may not support the desired renderer. - Renderable = major > 1 ? RenderableFlags.ES2 : RenderableFlags.ES; + + Renderable = RenderableFlags.GL; + if ((flags & GraphicsContextFlags.Embedded) != 0) + { + Renderable = major > 1 ? RenderableFlags.ES2 : RenderableFlags.ES; + } + + RenderApi api = (Renderable & RenderableFlags.GL) != 0 ? RenderApi.GL : RenderApi.ES; + Debug.Print("[EGL] Binding {0} rendering API.", api); + if (!Egl.BindAPI(api)) + { + Debug.Print("[EGL] Failed to bind rendering API. Error: {0}", Egl.GetError()); + } + Mode = new EglGraphicsMode().SelectGraphicsMode(window, mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, mode.AccumulatorFormat, mode.Buffers, mode.Stereo, @@ -133,7 +146,8 @@ namespace OpenTK.Platform.Egl if (Egl.SwapInterval(WindowInfo.Display, value)) swap_interval = value; else - Debug.Print("[Warning] Egl.SwapInterval({0}, {1}) failed.", WindowInfo.Display, value); + Debug.Print("[Warning] Egl.SwapInterval({0}, {1}) failed. Error: {2}", + WindowInfo.Display, value, Egl.GetError()); } } diff --git a/Source/OpenTK/Platform/Egl/EglGraphicsMode.cs b/Source/OpenTK/Platform/Egl/EglGraphicsMode.cs index bc78bc82..779e8ff2 100644 --- a/Source/OpenTK/Platform/Egl/EglGraphicsMode.cs +++ b/Source/OpenTK/Platform/Egl/EglGraphicsMode.cs @@ -34,6 +34,15 @@ namespace OpenTK.Platform.Egl { class EglGraphicsMode { + public GraphicsMode SelectGraphicsMode(EglWindowInfo window, + GraphicsMode mode, RenderableFlags flags) + { + return SelectGraphicsMode(window, + mode.ColorFormat, mode.Depth, mode.Stencil, + mode.Samples, mode.AccumulatorFormat, mode.Buffers, mode.Stereo, + flags); + } + public GraphicsMode SelectGraphicsMode(EglWindowInfo window, ColorFormat color, int depth, int stencil, int samples, ColorFormat accum, int buffers, bool stereo, diff --git a/Source/OpenTK/Platform/Egl/EglUnixContext.cs b/Source/OpenTK/Platform/Egl/EglUnixContext.cs index 5880138e..94eb2e43 100644 --- a/Source/OpenTK/Platform/Egl/EglUnixContext.cs +++ b/Source/OpenTK/Platform/Egl/EglUnixContext.cs @@ -28,14 +28,16 @@ #endregion using System; +using System.Diagnostics; using OpenTK.Graphics; namespace OpenTK.Platform.Egl { class EglUnixContext : EglContext { - readonly IntPtr ES1 = OpenTK.Platform.X11.DL.Open("libGLESv1_CM", X11.DLOpenFlags.Lazy); - readonly IntPtr ES2 = OpenTK.Platform.X11.DL.Open("libGLESv2", X11.DLOpenFlags.Lazy); + IntPtr ES1 = OpenTK.Platform.X11.DL.Open("libGLESv1_CM", X11.DLOpenFlags.Lazy); + IntPtr ES2 = OpenTK.Platform.X11.DL.Open("libGLESv2", X11.DLOpenFlags.Lazy); + IntPtr GL = OpenTK.Platform.X11.DL.Open("libGL", X11.DLOpenFlags.Lazy); public EglUnixContext(GraphicsMode mode, EglWindowInfo window, IGraphicsContext sharedContext, int major, int minor, GraphicsContextFlags flags) @@ -59,6 +61,10 @@ namespace OpenTK.Platform.Egl { return X11.DL.Symbol(ES2, function); } + else if ((renderable & RenderableFlags.GL) != 0 && GL != IntPtr.Zero) + { + return X11.DL.Symbol(GL, function); + } return IntPtr.Zero; } @@ -72,8 +78,32 @@ namespace OpenTK.Platform.Egl { X11.DL.Close(ES2); } + if (GL != IntPtr.Zero) + { + X11.DL.Close(GL); + } + + GL = ES1 = ES2 = IntPtr.Zero; base.Dispose(manual); } + + public override void LoadAll() + { + // Modern unices can use EGL to create + // both GL and ES contexts, so we need + // to load all entry points. This is + // especially true on KMS, Wayland and Mir. + + Stopwatch time = Stopwatch.StartNew(); + + new OpenTK.Graphics.OpenGL.GL().LoadEntryPoints(); + new OpenTK.Graphics.OpenGL4.GL().LoadEntryPoints(); + new OpenTK.Graphics.ES11.GL().LoadEntryPoints(); + new OpenTK.Graphics.ES20.GL().LoadEntryPoints(); + new OpenTK.Graphics.ES30.GL().LoadEntryPoints(); + + Debug.Print("Bindings loaded in {0} ms.", time.Elapsed.TotalMilliseconds); + } } } diff --git a/Source/OpenTK/Platform/Egl/EglWindowInfo.cs b/Source/OpenTK/Platform/Egl/EglWindowInfo.cs index db9acb49..4d0e1945 100644 --- a/Source/OpenTK/Platform/Egl/EglWindowInfo.cs +++ b/Source/OpenTK/Platform/Egl/EglWindowInfo.cs @@ -75,7 +75,7 @@ namespace OpenTK.Platform.Egl #region Public Members - public IntPtr Handle { get { return handle; } private set { handle = value; } } + public IntPtr Handle { get { return handle; } set { handle = value; } } public IntPtr Display { get { return display; } private set { display = value; } } @@ -87,7 +87,7 @@ namespace OpenTK.Platform.Egl if (Surface==IntPtr.Zero) { throw new GraphicsContextException(String.Format( - "[Error] Failed to create EGL window surface, error {0}.", Egl.GetError())); + "[EGL] Failed to create window surface, error {0}.", Egl.GetError())); } } diff --git a/Source/OpenTK/Platform/Factory.cs b/Source/OpenTK/Platform/Factory.cs index 9a701844..2bcfada5 100644 --- a/Source/OpenTK/Platform/Factory.cs +++ b/Source/OpenTK/Platform/Factory.cs @@ -54,6 +54,7 @@ namespace OpenTK.Platform // Create regular platform backend if (Configuration.RunningOnSdl2) Default = new SDL2.Sdl2Factory(); else if (Configuration.RunningOnX11) Default = new X11.X11Factory(); + else if (Configuration.RunningOnLinux) Default = new Linux.LinuxFactory(); else if (Configuration.RunningOnWindows) Default = new Windows.WinFactory(); else if (Configuration.RunningOnMacOS) Default = new MacOS.MacOSFactory(); else Default = new UnsupportedPlatform(); @@ -70,7 +71,8 @@ namespace OpenTK.Platform } else if (Egl.Egl.IsSupported) { - if (Configuration.RunningOnX11) Embedded = new Egl.EglX11PlatformFactory(); + if (Configuration.RunningOnLinux) Embedded = Default; + else if (Configuration.RunningOnX11) Embedded = new Egl.EglX11PlatformFactory(); else if (Configuration.RunningOnWindows) Embedded = new Egl.EglWinPlatformFactory(); else if (Configuration.RunningOnMacOS) Embedded = new Egl.EglMacPlatformFactory(); else Embedded = new UnsupportedPlatform(); diff --git a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs new file mode 100644 index 00000000..583dd449 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -0,0 +1,209 @@ +#region License +// +// Drm.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void VBlankCallback(int fd, + int sequence, + int tv_sec, + int tv_usec, + IntPtr user_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void PageFlipCallback(int fd, + int sequence, + int tv_sec, + int tv_usec, + IntPtr user_data); + + class Drm + { + const string lib = "libdrm"; + + [DllImport(lib, EntryPoint = "drmHandleEvent", CallingConvention = CallingConvention.Cdecl)] + public static extern int HandleEvent(int fd, ref EventContext evctx); + + [DllImport(lib, EntryPoint = "drmModeAddFB", CallingConvention = CallingConvention.Cdecl)] + public static extern int ModeAddFB(int fd, int width, int height, byte depth, + byte bpp, int pitch, int bo_handle, + out int buf_id); + + [DllImport(lib, EntryPoint = "drmModeRmFB", CallingConvention = CallingConvention.Cdecl)] + public static extern int ModeRmFB(int fd, int bufferId); + + [DllImport(lib, EntryPoint = "drmModeFreeCrtc", CallingConvention = CallingConvention.Cdecl)] + public static extern void ModeFreeCrtc(IntPtr ptr); + + [DllImport(lib, EntryPoint = "drmModeGetCrtc", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ModeGetCrtc(int fd, int crtcId); + + [DllImport(lib, EntryPoint = "drmModeFreeConnector", CallingConvention = CallingConvention.Cdecl)] + public static extern void ModeFreeConnector(IntPtr ptr); + + [DllImport(lib, EntryPoint = "drmModeFreeEncoder", CallingConvention = CallingConvention.Cdecl)] + public static extern void ModeFreeEncoder(IntPtr ptr); + + [DllImport(lib, EntryPoint = "drmModeGetConnector", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ModeGetConnector(int fd, int connector_id); + + [DllImport(lib, EntryPoint = "drmModeGetEncoder", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ModeGetEncoder(int fd, int encoder_id); + + [DllImport(lib, EntryPoint = "drmModeGetResources", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ModeGetResources(int fd); + + [DllImport(lib, EntryPoint = "drmModePageFlip", CallingConvention = CallingConvention.Cdecl)] + public static extern int ModePageFlip(int fd, int crtc_id, int fb_id, + PageFlipFlags flags, IntPtr user_data); + + [DllImport(lib, EntryPoint = "drmModeSetCrtc", CallingConvention = CallingConvention.Cdecl)] + unsafe public static extern int ModeSetCrtc(int fd, int crtcId, int bufferId, + int x, int y, int* connectors, int count, ModeInfo* mode); + + [DllImport(lib, EntryPoint = "drmModeSetCursor2", CallingConvention = CallingConvention.Cdecl)] + public static extern int SetCursor(int fd, int crtcId, int bo_handle, int width, int height, int hot_x, int hot_y); + + [DllImport(lib, EntryPoint = "drmModeMoveCursor", CallingConvention = CallingConvention.Cdecl)] + public static extern int MoveCursor(int fd, int crtcId, int x, int y); + + } + + enum ModeConnection + { + Connected = 1, + Disconnected = 2, + Unknown = 3 + } + + enum ModeSubPixel + { + Unknown = 1, + HorizontalRgb = 2, + HorizontalBgr = 3, + VerticalRgb = 4, + VerticalBgr = 5, + None = 6 + } + + [Flags] + enum PageFlipFlags + { + FlipEvent = 0x01, + FlipAsync = 0x02, + FlipFlags = FlipEvent | FlipAsync + } + + [StructLayout(LayoutKind.Sequential)] + struct EventContext + { + public int version; + public IntPtr vblank_handler; + public IntPtr page_flip_handler; + + public static readonly int Version = 2; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct ModeConnector + { + public int connector_id; + public int encoder_id; + public int connector_type; + public int connector_type_id; + public ModeConnection connection; + public int mmWidth, mmHeight; + public ModeSubPixel subpixel; + + public int count_modes; + public ModeInfo* modes; + + public int count_props; + public int *props; + public long *prop_values; + + public int count_encoders; + public int *encoders; + } + + struct ModeCrtc + { + public int crtc_id; + public int buffer_id; + + public int x, y; + public int width, height; + public int mode_valid; + public ModeInfo mode; + + public int gamma_size; + } + + struct ModeEncoder + { + public int encoder_id; + public int encoder_type; + public int crtc_id; + public int possible_crtcs; + public int possible_clones; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct ModeInfo + { + public uint clock; + public ushort hdisplay, hsync_start, hsync_end, htotal, hskew; + public ushort vdisplay, vsync_start, vsync_end, vtotal, vscan; + + public int vrefresh; // refresh rate * 1000 + + public uint flags; + public uint type; + public fixed sbyte name[32]; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct ModeRes + { + public int count_fbs; + public int* fbs; + public int count_crtcs; + public int* crtcs; + public int count_connectors; + public int* connectors; + public int count_encoders; + public int* encoders; + public int min_width, max_width; + public int min_height, max_height; + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs new file mode 100644 index 00000000..11988ebe --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs @@ -0,0 +1,452 @@ +#region License +// +// Evdev.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Diagnostics; +using OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + // Bindings for linux/input.h + class Evdev + { + #region KeyMap + + public static readonly Key[] KeyMap = new Key[] + { + // 0-7 + Key.Unknown, + Key.Escape, + Key.Number1, + Key.Number2, + Key.Number3, + Key.Number4, + Key.Number5, + Key.Number6, + // 8-15 + Key.Number7, + Key.Number8, + Key.Number9, + Key.Number0, + Key.Minus, + Key.Plus, + Key.BackSpace, + Key.Tab, + // 16-23 + Key.Q, + Key.W, + Key.E, + Key.R, + Key.T, + Key.Y, + Key.U, + Key.I, + // 24-31 + Key.O, + Key.P, + Key.BracketLeft, + Key.BracketRight, + Key.Enter, + Key.ControlLeft, + Key.A, + Key.S, + // 32-39 + Key.D, + Key.F, + Key.G, + Key.H, + Key.J, + Key.K, + Key.L, + Key.Semicolon, + // 40-47 + Key.Quote, + Key.Tilde, + Key.ShiftLeft, + Key.BackSlash, //Key.Execute, + Key.Z, + Key.X, + Key.C, + Key.V, //Key.Help, + // 48-55 + Key.B, + Key.N, + Key.M, + Key.Comma, + Key.Period, + Key.Slash, + Key.ShiftRight, + Key.KeypadMultiply, + // 56-63 + Key.AltLeft, + Key.Space, + Key.CapsLock, + Key.F1, + Key.F2, + Key.F3, + Key.F4, + Key.F5, + // 64-71 + Key.F6, + Key.F7, + Key.F8, + Key.F9, + Key.F10, + Key.NumLock, + Key.ScrollLock, + Key.Keypad7, + // 72-79 + Key.Keypad8, + Key.Keypad9, + Key.KeypadSubtract, + Key.Keypad4, + Key.Keypad5, + Key.Keypad6, + Key.KeypadPlus, + Key.Keypad1, + // 80-87 + Key.Keypad2, + Key.Keypad3, + Key.Keypad0, + Key.KeypadPeriod, + Key.Unknown, + Key.Unknown, // zenkakuhankaku + Key.Unknown, // 102ND + Key.F11, + // 88-95 + Key.F12, + Key.Unknown, // ro + Key.Unknown, // katakana + Key.Unknown, // hiragana + Key.Unknown, // henkan + Key.Unknown, // katakanahiragana + Key.Unknown, // muhenkan + Key.Unknown, // kpjpcomma + // 96-103 + Key.KeypadEnter, + Key.ControlRight, + Key.KeypadDivide, + Key.Unknown, // sysrq + Key.AltRight, + Key.Unknown, // linefeed + Key.Home, + Key.Up, + // 104-111 + Key.PageUp, + Key.Left, + Key.Right, + Key.End, + Key.Down, + Key.PageDown, + Key.Insert, + Key.Delete, + // 112-119 + Key.Unknown, // macro + Key.Unknown, // mute + Key.Unknown, // volumedown + Key.Unknown, // volumeup + Key.Unknown, // power + Key.Unknown, // kpequal + Key.Unknown, // kpplusminus + Key.Pause, + // 120-127 + Key.Unknown, // scale + Key.Unknown, // kpcomma + Key.Unknown, // hangeul / hanguel + Key.Unknown, // hanja + Key.Unknown, // yen + Key.WinLeft, + Key.WinRight, + Key.Unknown, // compose + // 128-135 + Key.Unknown, // stop + Key.Unknown, // again + Key.Unknown, // props + Key.Unknown, // undo + Key.Unknown, // front + Key.Unknown, // copy + Key.Unknown, // open + Key.Unknown, // paste + // 136-143 + Key.Unknown, // find + Key.Unknown, // cut + Key.Unknown, // help + Key.Unknown, // menu + Key.Unknown, // calc + Key.Unknown, // setup + Key.Unknown, // sleep + Key.Unknown, // wakeup + // 144-151 + Key.Unknown, // file + Key.Unknown, // send file + Key.Unknown, // delete file + Key.Unknown, // xfer + Key.Unknown, // prog1 + Key.Unknown, // prog2 + Key.Unknown, // www + Key.Unknown, // msdos + // 152-159 + Key.Unknown, // coffee / screenlock + Key.Unknown, // direction + Key.Unknown, // cycle windows + Key.Unknown, // mail + Key.Unknown, // bookmarks + Key.Unknown, // computer + Key.Back, + Key.Unknown, // forward + // 160-167 + Key.Unknown, // close cd + Key.Unknown, // eject cd + Key.Unknown, // eject/close cd + Key.Unknown, // next song + Key.Unknown, // play/pause + Key.Unknown, // previous song + Key.Unknown, // stop cd + Key.Unknown, // record + // 168-175 + Key.Unknown, // rewind + Key.Unknown, // phone + Key.Unknown, // iso + Key.Unknown, // config + Key.Unknown, // homepage + Key.Unknown, // refresh + Key.Unknown, // exit + Key.Unknown, // move, + // 176-183 + Key.Unknown, // edit, + Key.Unknown, // scroll up, + Key.Unknown, // scroll down, + Key.Unknown, // kp left paren, + Key.Unknown, // kp right paren, + Key.Unknown, // new, + Key.Unknown, // redo, + Key.F13, + // 184-191 + Key.F14, + Key.F15, + Key.F16, + Key.F17, + Key.F18, + Key.F19, + Key.F20, + Key.F21, + // 192-199 + Key.F22, + Key.F23, + Key.F24, + Key.Unknown, + Key.Unknown, + Key.Unknown, + Key.Unknown, + Key.Unknown, + // 200-207 + Key.Unknown, // play cd + Key.Unknown, // pause cd + Key.Unknown, // prog3 + Key.Unknown, // prog4 + Key.Unknown, // dashboard + Key.Unknown, // suspend + Key.Unknown, // close + Key.Unknown, // play + // 208-215 + Key.Unknown, // fast forward + Key.Unknown, // bass boost + Key.Unknown, // print + Key.Unknown, // hp + Key.Unknown, // camera + Key.Unknown, // sound + Key.Unknown, // question + Key.Unknown, // email + // 216-223 + Key.Unknown, // chat + Key.Unknown, // search + Key.Unknown, // connect + Key.Unknown, // finance + Key.Unknown, // sport + Key.Unknown, // shop + Key.Unknown, // alt erase + Key.Unknown, // cancel + // 224-231 + Key.Unknown, // brightness down + Key.Unknown, // brightness up + Key.Unknown, // media + Key.Unknown, // switch video mode + Key.Unknown, // dillum toggle + Key.Unknown, // dillum down + Key.Unknown, // dillum up + Key.Unknown, // send + // 232-239 + Key.Unknown, // reply + Key.Unknown, // forward email + Key.Unknown, // save + Key.Unknown, // documents + Key.Unknown, // battery + Key.Unknown, // bluetooth + Key.Unknown, // wlan + Key.Unknown, // uwb + // 240-247 + Key.Unknown, + Key.Unknown, // video next + Key.Unknown, // video prev + Key.Unknown, // brightness cycle + Key.Unknown, // brightness zero + Key.Unknown, // display off + Key.Unknown, // wwan / wimax + Key.Unknown, // rfkill + // 248-255 + Key.Unknown, // mic mute + Key.Unknown, + Key.Unknown, + Key.Unknown, + Key.Unknown, + Key.Unknown, + Key.Unknown, + Key.Unknown, // reserved + }; + + #endregion + + public static MouseButton GetMouseButton(EvdevButton button) + { + switch (button) + { + case EvdevButton.LEFT: + return MouseButton.Left; + case EvdevButton.RIGHT: + return MouseButton.Right; + case EvdevButton.MIDDLE: + return MouseButton.Middle; + case EvdevButton.BTN0: + return MouseButton.Button1; + case EvdevButton.BTN1: + return MouseButton.Button2; + case EvdevButton.BTN2: + return MouseButton.Button3; + case EvdevButton.BTN3: + return MouseButton.Button4; + case EvdevButton.BTN4: + return MouseButton.Button5; + case EvdevButton.BTN5: + return MouseButton.Button6; + case EvdevButton.BTN6: + return MouseButton.Button7; + case EvdevButton.BTN7: + return MouseButton.Button8; + case EvdevButton.BTN8: + return MouseButton.Button9; + default: + Debug.Print("[Input] Unknown EvdevButton {0}", button); + return MouseButton.Left; + } + } + } + + enum EvdevButton : uint + { + MISC = 0x100, + BTN0 = 0x100, + BTN1 = 0x101, + BTN2 = 0x102, + BTN3 = 0x103, + BTN4 = 0x104, + BTN5 = 0x105, + BTN6 = 0x106, + BTN7 = 0x107, + BTN8 = 0x108, + BTN9 = 0x109, + + MOUSE = 0x110, + LEFT = 0x110, + RIGHT = 0x111, + MIDDLE = 0x112, + SIDE = 0x113, + EXTRA = 0x114, + FORWARD = 0x115, + BACK = 0x116, + TASK = 0x117, + + JOYSTICK = 0x120, + TRIGGER = 0x120, + THUMB = 0x121, + THUMB2 = 0x122, + TOP = 0x123, + TOP2 = 0x124, + PINKIE = 0x125, + BASE = 0x126, + BASE2 = 0x127, + BASE3 = 0x128, + BASE4 = 0x129, + BASE5 = 0x12a, + BASE6 = 0x12b, + DEAD = 0x12f, + + GAMEPAD = 0x130, + SOUTH = 0x130, + A = SOUTH, + EAST = 0x131, + B = EAST, + C = 0x132, + NORTH = 0x133, + X = NORTH, + WEST = 0x134, + Y = WEST, + Z = 0x135, + TL = 0x136, + TR = 0x137, + TL2 = 0x138, + TR2 = 0x139, + SELECT = 0x13a, + START = 0x13b, + MODE = 0x13c, + THUMBL = 0x13d, + THUMBR = 0x13e, + + DIGI = 0x140, + TOOL_PEN = 0x140, + TOOL_RUBBER = 0x141, + TOOL_BRUSH = 0x142, + TOOL_PENCIL = 0x143, + TOOL_AIRBRUSH = 0x144, + TOOL_FINGER = 0x145, + TOOL_MOUSE = 0x146, + TOOL_LENS = 0x147, + TOOL_QUINTTAP = 0x148, // Five fingers on trackpad + TOUCH = 0x14a, + STYLUS = 0x14b, + STYLUS2 = 0x14c, + TOOL_DOUBLETAP = 0x14d, + TOOL_TRIPLETAP = 0x14e, + TOOL_QUADTAP = 0x14f, // Four fingers on trackpad + + WHEEL = 0x150, + GEAR_DOWN = 0x150, + GEAR_UP = 0x151, + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs new file mode 100644 index 00000000..d298d5ea --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -0,0 +1,278 @@ +#region License +// +// Gbm.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + using Device = IntPtr; // struct gbm_device* + using Surface = IntPtr; + using BufferObjectHandle = IntPtr; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void DestroyUserDataCallback(BufferObject bo, IntPtr data); + + class Gbm + { + const string lib = "gbm"; + + [DllImport(lib, EntryPoint = "gbm_bo_create", CallingConvention = CallingConvention.Cdecl)] + public static extern BufferObject CreateBuffer(Device gbm, int width, int height, SurfaceFormat format, SurfaceFlags flags); + + [DllImport(lib, EntryPoint = "gbm_bo_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyBuffer(BufferObject bo); + + [DllImport(lib, EntryPoint = "gbm_bo_write", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOWrite(IntPtr bo, IntPtr buf, IntPtr count); + + [DllImport(lib, EntryPoint = "gbm_bo_get_device", CallingConvention = CallingConvention.Cdecl)] + public static extern Device BOGetDevice(IntPtr bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_handle", CallingConvention = CallingConvention.Cdecl)] + public static extern BufferObjectHandle BOGetHandle(IntPtr bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_height", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOGetHeight(IntPtr bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_width", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOGetWidth(IntPtr bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_stride", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOGetStride(IntPtr bo); + + [DllImport(lib, EntryPoint = "gbm_bo_set_user_data", CallingConvention = CallingConvention.Cdecl)] + public static extern void BOSetUserData(IntPtr bo, IntPtr data, DestroyUserDataCallback callback); + + [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] + public static extern Device CreateDevice(int fd); + + [DllImport(lib, EntryPoint = "gbm_device_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyDevice(Device gbm); + + [DllImport(lib, EntryPoint = "gbm_device_get_fd", CallingConvention = CallingConvention.Cdecl)] + public static extern int DeviceGetFD(IntPtr gbm); + + [DllImport(lib, EntryPoint = "gbm_surface_create", CallingConvention = CallingConvention.Cdecl)] + public static extern Surface CreateSurface(Device gbm, int width, int height, SurfaceFormat format, SurfaceFlags flags); + + [DllImport(lib, EntryPoint = "gbm_surface_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroySurface(IntPtr surface); + + [DllImport(lib, EntryPoint = "gbm_device_is_format_supported", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool IsFormatSupported(Device gbm, SurfaceFormat format, SurfaceFlags usage); + + [DllImport(lib, EntryPoint = "gbm_surface_lock_front_buffer", CallingConvention = CallingConvention.Cdecl)] + public static extern BufferObject LockFrontBuffer(Surface surface); + + [DllImport(lib, EntryPoint = "gbm_surface_release_buffer", CallingConvention = CallingConvention.Cdecl)] + public static extern void ReleaseBuffer(Surface surface, BufferObject buffer); + } + + enum SurfaceFormat + { + BigEndian = 1 << 31, + C8 = ((int)('C') | ((int)('8') << 8) | ((int)(' ') << 16) | ((int)(' ') << 24)), + + RGB332 = ((int)('R') | ((int)('G') << 8) | ((int)('B') << 16) | ((int)('8') << 24)), + BGR233 = ((int)('B') | ((int)('G') << 8) | ((int)('R') << 16) | ((int)('8') << 24)), + + XRGB4444 = ((int)('X') | ((int)('R') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + XBGR4444 = ((int)('X') | ((int)('B') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + RGBX4444 = ((int)('R') | ((int)('X') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + BGRX4444 = ((int)('B') | ((int)('X') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + + ARGB4444 = ((int)('A') | ((int)('R') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + ABGR4444 = ((int)('A') | ((int)('B') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + RGBA4444 = ((int)('R') | ((int)('A') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + BGRA4444 = ((int)('B') | ((int)('A') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + + XRGB1555 = ((int)('X') | ((int)('R') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + XBGR1555 = ((int)('X') | ((int)('B') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + RGBX5551 = ((int)('R') | ((int)('X') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + BGRX5551 = ((int)('B') | ((int)('X') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + + ARGB1555 = ((int)('A') | ((int)('R') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + ABGR1555 = ((int)('A') | ((int)('B') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + RGBA5551 = ((int)('R') | ((int)('A') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + BGRA5551 = ((int)('B') | ((int)('A') << 8) | ((int)('1') << 16) | ((int)('5') << 24)), + + RGB565 = ((int)('R') | ((int)('G') << 8) | ((int)('1') << 16) | ((int)('6') << 24)), + BGR565 = ((int)('B') | ((int)('G') << 8) | ((int)('1') << 16) | ((int)('6') << 24)), + + RGB888 = ((int)('R') | ((int)('G') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + BGR888 = ((int)('B') | ((int)('G') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + + XRGB8888 = ((int)('X') | ((int)('R') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + XBGR8888 = ((int)('X') | ((int)('B') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + RGBX8888 = ((int)('R') | ((int)('X') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + BGRX8888 = ((int)('B') | ((int)('X') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + + ARGB8888 = ((int)('A') | ((int)('R') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + ABGR8888 = ((int)('A') | ((int)('B') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + RGBA8888 = ((int)('R') | ((int)('A') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + BGRA8888 = ((int)('B') | ((int)('A') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + + XRGB2101010 = ((int)('X') | ((int)('R') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + XBGR2101010 = ((int)('X') | ((int)('B') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + RGBX1010102 = ((int)('R') | ((int)('X') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + BGRX1010102 = ((int)('B') | ((int)('X') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + + ARGB2101010 = ((int)('A') | ((int)('R') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + ABGR2101010 = ((int)('A') | ((int)('B') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + RGBA1010102 = ((int)('R') | ((int)('A') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + BGRA1010102 = ((int)('B') | ((int)('A') << 8) | ((int)('3') << 16) | ((int)('0') << 24)), + + YUYV = ((int)('Y') | ((int)('U') << 8) | ((int)('Y') << 16) | ((int)('V') << 24)), + YVYU = ((int)('Y') | ((int)('V') << 8) | ((int)('Y') << 16) | ((int)('U') << 24)), + UYVY = ((int)('U') | ((int)('Y') << 8) | ((int)('V') << 16) | ((int)('Y') << 24)), + VYUY = ((int)('V') | ((int)('Y') << 8) | ((int)('U') << 16) | ((int)('Y') << 24)), + + AYUV = ((int)('A') | ((int)('Y') << 8) | ((int)('U') << 16) | ((int)('V') << 24)), + + NV12 = ((int)('N') | ((int)('V') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + NV21 = ((int)('N') | ((int)('V') << 8) | ((int)('2') << 16) | ((int)('1') << 24)), + NV16 = ((int)('N') | ((int)('V') << 8) | ((int)('1') << 16) | ((int)('6') << 24)), + NV61 = ((int)('N') | ((int)('V') << 8) | ((int)('6') << 16) | ((int)('1') << 24)), + + YUV410 = ((int)('Y') | ((int)('U') << 8) | ((int)('V') << 16) | ((int)('9') << 24)), + YVU410 = ((int)('Y') | ((int)('V') << 8) | ((int)('U') << 16) | ((int)('9') << 24)), + YUV411 = ((int)('Y') | ((int)('U') << 8) | ((int)('1') << 16) | ((int)('1') << 24)), + YVU411 = ((int)('Y') | ((int)('V') << 8) | ((int)('1') << 16) | ((int)('1') << 24)), + YUV420 = ((int)('Y') | ((int)('U') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + YVU420 = ((int)('Y') | ((int)('V') << 8) | ((int)('1') << 16) | ((int)('2') << 24)), + YUV422 = ((int)('Y') | ((int)('U') << 8) | ((int)('1') << 16) | ((int)('6') << 24)), + YVU422 = ((int)('Y') | ((int)('V') << 8) | ((int)('1') << 16) | ((int)('6') << 24)), + YUV444 = ((int)('Y') | ((int)('U') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + YVU444 = ((int)('Y') | ((int)('V') << 8) | ((int)('2') << 16) | ((int)('4') << 24)), + } + + [Flags] + enum SurfaceFlags + { + Scanout = (1 << 0), + Cursor64x64 = (1 << 1), + Rendering = (1 << 2), + Write = (1 << 3), + } + + [StructLayout(LayoutKind.Sequential)] + struct BufferObject : IEquatable + { + IntPtr buffer; + + public static readonly BufferObject Zero = + default(BufferObject); + + public int Write(byte[] data) + { + unsafe + { + fixed (byte* pdata = data) + { + return Gbm.BOWrite(buffer, (IntPtr)pdata, (IntPtr)data.Length); + } + } + } + + public void SetUserData(IntPtr data, DestroyUserDataCallback destroyFB) + { + Gbm.BOSetUserData(buffer, data, destroyFB); + } + + public Device Device + { + get { return Gbm.BOGetDevice(buffer); } + } + + public int Handle + { + get { return Gbm.BOGetHandle(buffer).ToInt32(); } + } + + public int Width + { + get { return Gbm.BOGetWidth(buffer); } + } + + public int Height + { + get { return Gbm.BOGetHeight(buffer); } + } + + public int Stride + { + get { return Gbm.BOGetStride(buffer); } + } + + public void Dispose() + { + Gbm.DestroyBuffer(this); + buffer = IntPtr.Zero; + } + + public static bool operator ==(BufferObject left, BufferObject right) + { + return left.Equals(right); + } + + public static bool operator !=(BufferObject left, BufferObject right) + { + return !left.Equals(right); + } + + public override bool Equals(object obj) + { + return + obj is BufferObject && + this.Equals((BufferObject)obj); + } + + public override int GetHashCode() + { + return buffer.GetHashCode(); + } + + public override string ToString() + { + return string.Format("[BufferObject: {0}]", buffer); + } + + #region IEquatable implementation + + public bool Equals(BufferObject other) + { + return buffer == other.buffer; + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Kms.cs b/Source/OpenTK/Platform/Linux/Bindings/Kms.cs new file mode 100644 index 00000000..a5c7f48b --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Kms.cs @@ -0,0 +1,46 @@ +#region License +// +// Kms.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + class Kms + { + const string lib = "libkms"; + + [DllImport(lib, EntryPoint = "kms_bo_map", CallingConvention = CallingConvention.Cdecl)] + public static extern int MapBuffer(IntPtr bo, out IntPtr @out); + + [DllImport(lib, EntryPoint = "kms_bo_unmap", CallingConvention = CallingConvention.Cdecl)] + public static extern int UnmapBuffer(IntPtr bo); + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs new file mode 100644 index 00000000..8dd2b69d --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -0,0 +1,330 @@ +#region License +// +// LibInput.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +#pragma warning disable 0169, 0219 + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate int OpenRestrictedCallback(IntPtr path, int flags, IntPtr data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + delegate void CloseRestrictedCallback(int fd, IntPtr data); + + class LibInput + { + internal const string lib = "libinput"; + + [DllImport(lib, EntryPoint = "libinput_udev_create_for_seat", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateContext(InputInterface @interface, + IntPtr user_data, IntPtr udev, string seat_id); + + [DllImport(lib, EntryPoint = "libinput_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyContext(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_event_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyEvent(IntPtr @event); + + [DllImport(lib, EntryPoint = "libinput_device_get_sysname", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DeviceGetNameInternal(IntPtr device); + public static string DeviceGetName(IntPtr device) + { + unsafe + { + return new string((sbyte*)DeviceGetNameInternal(device)); + } + } + + [DllImport(lib, EntryPoint = "libinput_device_get_user_data", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr DeviceGetData(IntPtr device); + + [DllImport(lib, EntryPoint = "libinput_device_set_user_data", CallingConvention = CallingConvention.Cdecl)] + public static extern void DeviceSetData(IntPtr device, IntPtr user_data); + + [DllImport(lib, EntryPoint = "libinput_device_get_output_name", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr DeviceGetOutputNameInternal(IntPtr device); + public static string DeviceGetOutputName(IntPtr device) + { + unsafe + { + sbyte* pname = (sbyte*)DeviceGetOutputNameInternal(device); + return pname == null ? String.Empty : new string(pname); + } + } + + [DllImport(lib, EntryPoint = "libinput_device_get_seat", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr DeviceGetSeat(IntPtr device); + + [DllImport(lib, EntryPoint = "libinput_device_has_capability", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeviceHasCapability(IntPtr device, DeviceCapability capability); + + [DllImport(lib, EntryPoint = "libinput_dispatch", CallingConvention = CallingConvention.Cdecl)] + public static extern int Dispatch(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_event_get_device", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr GetDevice(IntPtr @event); + + [DllImport(lib, EntryPoint = "libinput_get_event", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr GetEvent(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_event_get_keyboard_event", CallingConvention = CallingConvention.Cdecl)] + public static extern KeyboardEvent GetKeyboardEvent(IntPtr @event); + + [DllImport(lib, EntryPoint = "libinput_event_get_pointer_event", CallingConvention = CallingConvention.Cdecl)] + public static extern PointerEvent GetPointerEvent(IntPtr @event); + + [DllImport(lib, EntryPoint = "libinput_event_get_type", CallingConvention = CallingConvention.Cdecl)] + public static extern InputEventType GetEventType(IntPtr @event); + + [DllImport(lib, EntryPoint = "libinput_get_fd", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetFD(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_next_event_type", CallingConvention = CallingConvention.Cdecl)] + public static extern InputEventType NextEventType(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_resume", CallingConvention = CallingConvention.Cdecl)] + public static extern void Resume(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_suspend", CallingConvention = CallingConvention.Cdecl)] + public static extern void Suspend(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_seat_get_logical_name", CallingConvention = CallingConvention.Cdecl)] + static extern public IntPtr SeatGetLogicalNameInternal(IntPtr seat); + public static string SeatGetLogicalName(IntPtr seat) + { + unsafe + { + return new string((sbyte*)SeatGetLogicalNameInternal(seat)); + } + } + + [DllImport(lib, EntryPoint = "libinput_seat_get_physical_name", CallingConvention = CallingConvention.Cdecl)] + static extern public IntPtr SeatGetPhysicalNameInternal(IntPtr seat); + public static string SeatGetPhysicalName(IntPtr seat) + { + unsafe + { + return new string((sbyte*)SeatGetPhysicalNameInternal(seat)); + } + } + } + + enum DeviceCapability + { + Keyboard = 0, + Mouse, + Touch + } + + enum InputEventType + { + None = 0, + + DeviceAdded, + DeviceRemoved, + + KeyboardKey = 300, + + PointerMotion = 400, + PointerMotionAbsolute, + PointerButton, + PointerAxis, + + TouchDown = 500, + TouchUP, + TouchMotion, + TouchCancel, + + /// \internal + /// + /// Signals the end of a set of touchpoints at one device sample + /// time. This event has no coordinate information attached. + /// + TouchFrame + } + + enum ButtonState + { + Released = 0, + Pressed = 1 + } + + enum KeyState + { + Released = 0, + Pressed = 1 + } + + enum PointerAxis + { + VerticalScroll = 0, + HorizontalScroll = 1 + } + + struct Fixed24 + { + internal readonly int Value; + + public static implicit operator double(Fixed24 n) + { + long l = ((1023L + 44L) << 52) + (1L << 51) + n.Value; + unsafe + { + double d = *(double*)&l; + return d - (3L << 43); + } + } + + public static implicit operator float(Fixed24 n) + { + return (float)(double)n; + } + + public static explicit operator int(Fixed24 n) + { + return n.Value >> 8; + } + } + + [StructLayout(LayoutKind.Sequential)] + class InputInterface + { + internal readonly IntPtr open; + internal readonly IntPtr close; + + public InputInterface( + OpenRestrictedCallback open_restricted, + CloseRestrictedCallback close_restricted) + { + if (open_restricted == null || close_restricted == null) + throw new ArgumentNullException(); + + open = Marshal.GetFunctionPointerForDelegate(open_restricted); + close = Marshal.GetFunctionPointerForDelegate(close_restricted); + } + } + + [StructLayout(LayoutKind.Sequential)] + struct KeyboardEvent + { + IntPtr @event; + + public IntPtr BaseEvent { get { return GetBaseEvent(@event); } } + public IntPtr Event { get { return @event; } } + public uint Time { get { return GetTime(@event); } } + public uint Key { get { return GetKey(@event); } } + public uint KeyCount { get { return GetSeatKeyCount(@event); } } + public KeyState KeyState { get { return GetKeyState(@event); } } + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_time", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetTime(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_base_event", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr GetBaseEvent(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_seat_key_count", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetSeatKeyCount(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_key", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetKey(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_keyboard_get_key_state", CallingConvention = CallingConvention.Cdecl)] + static extern KeyState GetKeyState(IntPtr @event); + } + + + [StructLayout(LayoutKind.Sequential)] + struct PointerEvent + { + IntPtr @event; + + public IntPtr BaseEvent { get { return GetBaseEvent(@event); } } + public IntPtr Event { get { return @event; } } + public uint Time { get { return GetTime(@event); } } + public EvdevButton Button { get { return (EvdevButton)GetButton(@event); } } + public uint ButtonCount { get { return GetButtonCount(@event); } } + public ButtonState ButtonState { get { return GetButtonState(@event); } } + public PointerAxis Axis { get { return GetAxis(@event); } } + public Fixed24 AxisValue { get { return GetAxisValue(@event); } } + public Fixed24 DeltaX { get { return GetDX(@event); } } + public Fixed24 DeltaY { get { return GetDY(@event); } } + public Fixed24 X { get { return GetAbsX(@event); } } + public Fixed24 Y { get { return GetAbsY(@event); } } + public Fixed24 TransformedX(int width) { return GetAbsXTransformed(@event, width); } + public Fixed24 TransformedY(int height) { return GetAbsYTransformed(@event, height); } + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_time", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetTime(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_base_event", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr GetBaseEvent(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_seat_key_count", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetSeatKeyCount(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_button", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetButton(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_seat_button_count", CallingConvention = CallingConvention.Cdecl)] + static extern uint GetButtonCount(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_button_state", CallingConvention = CallingConvention.Cdecl)] + static extern ButtonState GetButtonState(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_axis", CallingConvention = CallingConvention.Cdecl)] + static extern PointerAxis GetAxis(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_axis_value", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAxisValue(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_dx", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetDX(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_dy", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetDY(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_x", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsX(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_y", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsY(IntPtr @event); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_x_transformed", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsXTransformed(IntPtr @event, int width); + + [DllImport(LibInput.lib, EntryPoint = "libinput_event_pointer_get_absolute_y_transformed", CallingConvention = CallingConvention.Cdecl)] + static extern Fixed24 GetAbsYTransformed(IntPtr @event, int height); + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs new file mode 100644 index 00000000..db026a77 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -0,0 +1,178 @@ +#region License +// +// Linux.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace OpenTK.Platform.Linux +{ + partial class Libc + { + const string lib = "libc"; + + [DllImport(lib)] + public static extern int dup(int file); + + [DllImport(lib)] + public static extern int dup2(int file1, int file2); + + [DllImport(lib)] + public static extern int ioctl(int d, JoystickIoctlCode request, ref int data); + + [DllImport(lib)] + public static extern int ioctl(int d, JoystickIoctlCode request, StringBuilder data); + + [DllImport(lib)] + public static extern int ioctl(int d, EvdevIoctlCode request, out EvdevInputId data); + + [DllImport(lib)] + public static extern int ioctl(int d, KeyboardIoctlCode request, ref IntPtr data); + + [DllImport(lib)] + public static extern int ioctl(int d, KeyboardIoctlCode request, int data); + + [DllImport(lib)] + public static extern int open([MarshalAs(UnmanagedType.LPStr)]string pathname, OpenFlags flags); + + [DllImport(lib)] + public static extern int open(IntPtr pathname, OpenFlags flags); + + [DllImport(lib)] + public static extern int close(int fd); + + [DllImport(lib)] + unsafe public static extern IntPtr read(int fd, void* buffer, UIntPtr count); + + public static int read(int fd, out byte b) + { + unsafe + { + fixed (byte* pb = &b) + { + return read(fd, pb, (UIntPtr)1).ToInt32(); + } + } + } + + public static int read(int fd, out short s) + { + unsafe + { + fixed (short* ps = &s) + { + return read(fd, ps, (UIntPtr)2).ToInt32(); + } + } + } + + [DllImport(lib)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool isatty(int fd); + } + + enum ErrorNumber + { + Interrupted = 4, + Again = 11, + InvalidValue = 22, + } + + [Flags] + enum OpenFlags + { + ReadOnly = 0x0000, + WriteOnly = 0x0001, + ReadWrite = 0x0002, + NonBlock = 0x0800, + CloseOnExec = 0x0080000 + } + + enum EvdevIoctlCode : uint + { + Id = ((byte)'E' << 8) | (0x02 << 0) //EVIOCGID, which is _IOR('E', 0x02, struct input_id) + } + + [Flags] + enum JoystickEventType : byte + { + Button = 0x01, // button pressed/released + Axis = 0x02, // joystick moved + Init = 0x80 // initial state of device + } + + enum JoystickIoctlCode : uint + { + Version = 0x80046a01, + Axes = 0x80016a11, + Buttons = 0x80016a12, + Name128 = (2u << 30) | (0x6A << 8) | (0x13 << 0) | (128 << 16) //JSIOCGNAME(128), which is _IOC(_IO_READ, 'j', 0x13, len) + } + + enum KeyboardIoctlCode + { + GetMode = 0x4b44, + SetMode = 0x4b45, + } + + [StructLayout(LayoutKind.Sequential)] + struct Stat + { + public IntPtr dev; /* ID of device containing file */ + public IntPtr ino; /* inode number */ + public IntPtr mode; /* protection */ + public IntPtr nlink; /* number of hard links */ + public IntPtr uid; /* user ID of owner */ + public IntPtr gid; /* group ID of owner */ + public IntPtr rdev; /* device ID (if special file) */ + public IntPtr size; /* total size, in bytes */ + public IntPtr blksize; /* blocksize for file system I/O */ + public IntPtr blocks; /* number of 512B blocks allocated */ + public IntPtr atime; /* time of last access */ + public IntPtr mtime; /* time of last modification */ + public IntPtr ctime; /* time of last status change */ + } + + struct EvdevInputId + { + public ushort BusType; + public ushort Vendor; + public ushort Product; + public ushort Version; + } + + struct JoystickEvent + { + public uint Time; // (u32) event timestamp in milliseconds + public short Value; // (s16) value + public JoystickEventType Type; // (u8) event type + public byte Number; // (u8) axis/button number + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs new file mode 100644 index 00000000..f78d6d73 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs @@ -0,0 +1,65 @@ +#region License +// +// Poll.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + partial class Libc + { + [DllImport(lib, CallingConvention = CallingConvention.Cdecl, SetLastError = true)] + public static extern int poll(ref PollFD fd, IntPtr fd_count, int timeout); + + public static int poll(ref PollFD fd, int fd_count, int timeout) + { + return poll(ref fd, (IntPtr)fd_count, timeout); + } + } + + [Flags] + enum PollFlags : short + { + In = 0x01, + Pri = 0x02, + Out = 0x04, + Error = 0x08, + Hup = 0x10, + Invalid = 0x20, + } + + [StructLayout(LayoutKind.Sequential)] + struct PollFD + { + public int fd; + public PollFlags events; + public PollFlags revents; + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs new file mode 100644 index 00000000..e72ad741 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs @@ -0,0 +1,170 @@ +#region License +// +// Terminal.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + class Terminal + { + const string lib = "libc"; + + [DllImport(lib, EntryPoint = "isatty", CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.I4)] + public static extern bool IsTerminal(int fd); + + [DllImport(lib, EntryPoint = "tcgetattr", CallingConvention = CallingConvention.Cdecl)] + public static extern int GetAttributes(int fd, out TerminalState state); + + [DllImport(lib, EntryPoint = "tcsetattr", CallingConvention = CallingConvention.Cdecl)] + public static extern int SetAttributes(int fd, OptionalActions actions, ref TerminalState state); + } + + [Flags] + enum InputFlags + { + IGNBRK = 1 << 0, + BRKINT = 1 << 1, + IGNPAR = 1 << 2, + PARMRK = 1 << 3, + INPCK = 1 << 4, + ISTRIP = 1 << 5, + INLCR = 1 << 6, + IGNCR = 1 << 7, + ICRNL = 1 << 8, + IUCLC = 1 << 9, + IXON = 1 << 10, + IXANY = 1 << 11, + IXOFF = 1 << 12, + IMAXBEL = 1 << 13, + IUTF8 = 1 << 14, + } + + [Flags] + enum OutputFlags + { + OPOST = 1 << 1, + OLCUC = 1 << 2, + ONLCR = 1 << 3, + OCRNL = 1 << 4, + ONOCR = 1 << 5, + ONLRET = 1 << 6, + OFILL = 1 << 7, + OFDEL = 1 << 8, + } + + [Flags] + enum ControlFlags + { + B0 = 0, // hang up + B50, + B75, + B110, + B134, + B150, + B200, + B300, + B600, + B1200, + B1800, + B2400, + B4800, + B9600, + B19200, + B38400, + } + + [Flags] + enum LocalFlags + { + ISIG = 0x01, + ICANON = 0x02, + ECHO = 0x08, + } + + enum OptionalActions + { + NOW = 0, + DRAIN = 1, + FLUSH = 2 + } + + [StructLayout(LayoutKind.Sequential)] + struct TerminalState + { + public InputFlags InputMode; + public OutputFlags OutputMode; + public ControlFlags ControlMode; + public LocalFlags LocalMode; + public byte LineDiscipline; + public ControlCharacters ControlCharacters; + public int InputSpeed; + public int OutputSpeed; + } + + [StructLayout(LayoutKind.Sequential)] + struct ControlCharacters + { + public byte VINTR; + public byte VQUIT; + public byte VERASE; + public byte VKILL; + public byte VEOF; + public byte VTIME; + public byte VMIN; + public byte VSWTC; + public byte VSTART; + public byte VSTOP; + public byte VSUSP; + public byte VEOL; + public byte VREPRINT; + public byte VDISCARD; + public byte VWERASE; + public byte VLNEXT; + public byte VEOL2; + public byte C17; + public byte C18; + public byte C19; + public byte C20; + public byte C21; + public byte C22; + public byte C23; + public byte C24; + public byte C25; + public byte C26; + public byte C27; + public byte C28; + public byte C29; + public byte C30; + public byte C31; + + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Udev.cs b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs new file mode 100644 index 00000000..4c341b70 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs @@ -0,0 +1,46 @@ +#region License +// +// Udev.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace OpenTK.Platform.Linux +{ + class Udev + { + const string lib = "libudev"; + + [DllImport(lib, EntryPoint = "udev_new", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr New(); + + [DllImport(lib, EntryPoint = "udev_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void Destroy(IntPtr Udev); + } +} + diff --git a/Source/OpenTK/Platform/Linux/DefaultCursor.cs b/Source/OpenTK/Platform/Linux/DefaultCursor.cs new file mode 100644 index 00000000..40e58e57 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/DefaultCursor.cs @@ -0,0 +1,76 @@ +#region License +// +// DefaultCursor.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; + +namespace OpenTK.Platform.Linux +{ + static class Cursors + { + public static readonly MouseCursor Default = + new MouseCursor(8, 4, 32, 32, new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1A, 0x1A, 0x1A, 0x1F, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0xF1, 0xF1, 0xF1, 0xF3, 0x16, 0x16, 0x16, 0x2B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xEC, 0xEC, 0xEC, 0xF4, 0x11, 0x11, 0x11, 0x37, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0xE9, 0xE9, 0xF3, 0x0C, 0x0C, 0x0C, 0x33, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x99, 0x99, 0x99, 0xFF, 0xF7, 0xF7, 0xF7, 0xFF, 0xE4, 0xE4, 0xE4, 0xF0, 0x07, 0x07, 0x07, 0x2D, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0x61, 0x61, 0xFF, 0x52, 0x52, 0x52, 0xFF, 0xF9, 0xF9, 0xF9, 0xFF, 0xE0, 0xE0, 0xE0, 0xEC, 0x03, 0x03, 0x03, 0x29, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x5F, 0x5F, 0xFF, 0x13, 0x13, 0x13, 0xFF, 0x5C, 0x5C, 0x5C, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0xD9, 0xD9, 0xD9, 0xE7, 0x01, 0x01, 0x01, 0x26, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x5C, 0x5C, 0x5C, 0xFF, 0x10, 0x10, 0x10, 0xFF, 0x19, 0x19, 0x19, 0xFF, 0x66, 0x66, 0x66, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0xD3, 0xD3, 0xD3, 0xE2, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x59, 0x59, 0x59, 0xFF, 0x0C, 0x0C, 0x0C, 0xFF, 0x15, 0x15, 0x15, 0xFF, 0x1E, 0x1E, 0x1E, 0xFF, 0x72, 0x72, 0x72, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0xCA, 0xCA, 0xCA, 0xDB, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x08, 0x08, 0x08, 0xFF, 0x11, 0x11, 0x11, 0xFF, 0x1A, 0x1A, 0x1A, 0xFF, 0x24, 0x24, 0x24, 0xFF, 0x7C, 0x7C, 0x7C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC1, 0xC1, 0xC1, 0xD4, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x04, 0x04, 0x04, 0xFF, 0x0D, 0x0D, 0x0D, 0xFF, 0x16, 0x16, 0x16, 0xFF, 0x20, 0x20, 0x20, 0xFF, 0x29, 0x29, 0x29, 0xFF, 0x88, 0x88, 0x88, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xB7, 0xB7, 0xB7, 0xCD, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x09, 0x09, 0x09, 0xFF, 0x12, 0x12, 0x12, 0xFF, 0x1C, 0x1C, 0x1C, 0xFF, 0x25, 0x25, 0x25, 0xFF, 0x2F, 0x2F, 0x2F, 0xFF, 0x92, 0x92, 0x92, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAD, 0xAD, 0xAD, 0xC5, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x05, 0x05, 0x05, 0xFF, 0x0E, 0x0E, 0x0E, 0xFF, 0x18, 0x18, 0x18, 0xFF, 0x21, 0x21, 0x21, 0xFF, 0x2B, 0x2B, 0x2B, 0xFF, 0x34, 0x34, 0x34, 0xFF, 0x9C, 0x9C, 0x9C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA1, 0xA1, 0xA1, 0xBC, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x01, 0x01, 0xFF, 0x0A, 0x0A, 0x0A, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x1D, 0x1D, 0x1D, 0xFF, 0x27, 0x27, 0x27, 0xFF, 0x30, 0x30, 0x30, 0xFF, 0x39, 0x39, 0x39, 0xFF, 0xA7, 0xA7, 0xA7, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0x96, 0x96, 0x96, 0xB3, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x07, 0x07, 0x07, 0xFF, 0x10, 0x10, 0x10, 0xFF, 0x19, 0x19, 0x19, 0xFF, 0x23, 0x23, 0x23, 0xFF, 0x2C, 0x2C, 0x2C, 0xFF, 0x35, 0x35, 0x35, 0xFF, 0x3F, 0x3F, 0x3F, 0xFF, 0xB0, 0xB0, 0xB0, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0x8B, 0x8B, 0x8B, 0xAB, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0x03, 0x03, 0xFF, 0x0C, 0x0C, 0x0C, 0xFF, 0x15, 0x15, 0x15, 0xFF, 0x1F, 0x1F, 0x1F, 0xFF, 0x28, 0x28, 0x28, 0xFF, 0x31, 0x31, 0x31, 0xFF, 0x3B, 0x3B, 0x3B, 0xFF, 0x45, 0x45, 0x45, 0xFF, 0xB7, 0xB7, 0xB7, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0x80, 0x80, 0x80, 0xA2, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x08, 0x08, 0x08, 0xFF, 0x11, 0x11, 0x11, 0xFF, 0x1F, 0x1F, 0x1F, 0xFF, 0x61, 0x61, 0x61, 0xFF, 0x69, 0x69, 0x69, 0xFF, 0x6F, 0x6F, 0x6F, 0xFF, 0x76, 0x76, 0x76, 0xFF, 0x7D, 0x7D, 0x7D, 0xFF, 0xE1, 0xE1, 0xE1, 0xFF, 0xFD, 0xFD, 0xFD, 0xFF, 0x75, 0x75, 0x75, 0x97, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x04, 0x04, 0x04, 0xFF, 0x0D, 0x0D, 0x0D, 0xFF, 0x17, 0x17, 0x17, 0xFF, 0xCD, 0xCD, 0xCD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x66, 0x66, 0x85, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x5B, 0x5B, 0x5B, 0xFF, 0x10, 0x10, 0x10, 0xFF, 0x13, 0x13, 0x13, 0xFF, 0x61, 0x61, 0x61, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x69, 0x69, 0x69, 0xBA, 0x1E, 0x1E, 0x1E, 0x83, 0x1E, 0x1E, 0x1E, 0x78, 0x1E, 0x1E, 0x1E, 0x77, 0x1E, 0x1E, 0x1E, 0x74, 0x1E, 0x1E, 0x1E, 0x6B, 0x15, 0x15, 0x15, 0x4A, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x6D, 0x6D, 0x6D, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0x6D, 0x6D, 0x6D, 0xFF, 0x0F, 0x0F, 0x0F, 0xFF, 0x19, 0x19, 0x19, 0xFF, 0xDE, 0xDE, 0xDE, 0xFF, 0xDB, 0xDB, 0xDB, 0xEE, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x75, 0x75, 0x75, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xF7, 0xF7, 0xFC, 0xE6, 0xE6, 0xE6, 0xFF, 0x10, 0x10, 0x10, 0xFF, 0x14, 0x14, 0x14, 0xFF, 0x71, 0x71, 0x71, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0x36, 0x36, 0x36, 0x75, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0x58, 0x58, 0x58, 0xFF, 0x7E, 0x7E, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAF, 0xAF, 0xAF, 0xD9, 0x36, 0x36, 0x36, 0x8F, 0xFD, 0xFD, 0xFD, 0xFF, 0x67, 0x67, 0x67, 0xFF, 0x10, 0x10, 0x10, 0xFF, 0x1E, 0x1E, 0x1E, 0xFF, 0xEA, 0xEA, 0xEA, 0xFF, 0xCD, 0xCD, 0xCD, 0xE2, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0xCC, 0xCC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA7, 0xA7, 0xA7, 0xD5, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x3A, 0xD5, 0xD5, 0xD5, 0xE3, 0xE1, 0xE1, 0xE1, 0xFF, 0x0F, 0x0F, 0x0F, 0xFF, 0x16, 0x16, 0x16, 0xFF, 0x81, 0x81, 0x81, 0xFF, 0xFC, 0xFC, 0xFC, 0xFF, 0x20, 0x20, 0x20, 0x5E, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFF, 0x9F, 0x9F, 0x9F, 0xCF, 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x12, 0x3D, 0x3D, 0x3D, 0x59, 0xFD, 0xFD, 0xFD, 0xFF, 0x61, 0x61, 0x61, 0xFF, 0x12, 0x12, 0x12, 0xFF, 0x25, 0x25, 0x25, 0xFF, 0xFA, 0xFA, 0xFA, 0xFF, 0x97, 0x97, 0x97, 0xC0, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0D, 0xFF, 0xFF, 0xFF, 0xFF, 0x97, 0x97, 0x97, 0xC1, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0xD9, 0xD9, 0xD9, 0xE5, 0xDC, 0xDC, 0xDC, 0xFF, 0x19, 0x19, 0x19, 0xFF, 0x34, 0x34, 0x34, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xA2, 0xA2, 0xA2, 0xCB, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0B, 0x8E, 0x8E, 0x8E, 0xA1, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x45, 0x45, 0x45, 0x5F, 0xFA, 0xFA, 0xFA, 0xFF, 0xF2, 0xF2, 0xF2, 0xFF, 0xFB, 0xFB, 0xFB, 0xFF, 0xF2, 0xF2, 0xF2, 0xFC, 0x30, 0x30, 0x30, 0x83, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0E, 0x34, 0x34, 0x34, 0x5D, 0xBB, 0xBB, 0xBB, 0xD5, 0x95, 0x95, 0x95, 0xC4, 0x16, 0x16, 0x16, 0x72, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }); + + public static readonly MouseCursor Empty = MouseCursor.Empty; + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs new file mode 100644 index 00000000..d347a6aa --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -0,0 +1,413 @@ +#region License +// +// LinuxDisplayDriver.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using OpenTK; +using OpenTK.Graphics; + +namespace OpenTK.Platform.Linux +{ + // Stores platform-specific information about a display + class LinuxDisplay + { + public int FD; + public IntPtr Connector; + public IntPtr Crtc; + public IntPtr Encoder; + + unsafe public ModeConnector* pConnector { get { return (ModeConnector*)Connector; } } + unsafe public ModeCrtc* pCrtc { get { return (ModeCrtc*)Crtc; } } + unsafe public ModeEncoder* pEncoder { get { return (ModeEncoder*)Encoder; } } + /* + public ModeInfo Mode + { + get + { + if (Crtc == IntPtr.Zero) + throw new InvalidOperationException(); + + unsafe + { + return pCrtc->mode; + } + } + } + */ + + public ModeInfo OriginalMode; + + public int Id + { + get + { + if (Crtc == IntPtr.Zero) + throw new InvalidOperationException(); + + unsafe + { + return (int)pCrtc->crtc_id; + } + } + } + + public LinuxDisplay(int fd, IntPtr c, IntPtr e, IntPtr r) + { + FD = fd; + Connector = c; + Encoder = e; + Crtc = r; + unsafe + { + OriginalMode = pCrtc->mode; // in case we change resolution later on + } + } + } + + class LinuxDisplayDriver : DisplayDeviceBase + { + readonly int FD; + readonly Dictionary DisplayIds = + new Dictionary(); + + public LinuxDisplayDriver(int fd) + { + Debug.Print("[KMS] Creating LinuxDisplayDriver for fd:{0}", fd); + Debug.Indent(); + try + { + FD = fd; + UpdateDisplays(fd); + } + finally + { + Debug.Unindent(); + } + } + + /// \internal + /// + /// Queries the specified GPU for connected displays and, optionally, + /// returns the list of displays. + /// + /// true, if at least one display is connected, false otherwise. + /// The fd for the GPU to query, obtained through open("/dev/dri/card0"). + /// + /// If not null, this will contain a list instances, + /// one for each connected display. + /// + internal static bool QueryDisplays(int fd, List displays) + { + unsafe + { + bool has_displays = false; + if (displays != null) + { + displays.Clear(); + } + + ModeRes* resources = (ModeRes*)Drm.ModeGetResources(fd); + if (resources == null) + { + Debug.Print("[KMS] Drm.ModeGetResources failed."); + return false; + } + Debug.Print("[KMS] DRM found {0} connectors", resources->count_connectors); + + // Search for a valid connector + ModeConnector* connector = null; + for (int i = 0; i < resources->count_connectors; i++) + { + connector = (ModeConnector*)Drm.ModeGetConnector(fd, + *(resources->connectors + i)); + if (connector != null) + { + bool success = false; + LinuxDisplay display = null; + try + { + if (connector->connection == ModeConnection.Connected && + connector->count_modes > 0) + { + success = QueryDisplay(fd, connector, out display); + has_displays |= success; + } + } + catch (Exception e) + { + Debug.Print("[KMS] Failed to add display. Error: {0}", e); + } + + if (success && displays != null) + { + displays.Add(display); + } + else + { + Drm.ModeFreeConnector((IntPtr)connector); + connector = null; + } + } + } + + return has_displays; + } + } + + void UpdateDisplays(int fd) + { + unsafe + { + lock (this) + { + AvailableDevices.Clear(); + DisplayIds.Clear(); + + List displays = new List(); + if (QueryDisplays(fd, displays)) + { + foreach (LinuxDisplay display in displays) + { + AddDisplay(display); + } + } + + if (AvailableDevices.Count == 0) + { + Debug.Print("[KMS] Failed to find any active displays"); + } + } + } + } + + unsafe static ModeEncoder* GetEncoder(int fd, ModeConnector* c) + { + ModeEncoder* encoder = null; + for (int i = 0; i < c->count_encoders && encoder == null; i++) + { + ModeEncoder* e = (ModeEncoder*)Drm.ModeGetEncoder( + fd, *(c->encoders + i)); + if (e != null) + { + if (e->encoder_id == c->encoder_id) + { + encoder = e; + } + else + { + Drm.ModeFreeEncoder((IntPtr)e); + } + } + } + + if (encoder != null) + { + Debug.Print("[KMS] Encoder {0} found for connector {1}", + encoder->encoder_id, c->connector_id); + } + else + { + Debug.Print("[KMS] Failed to find encoder for connector {0}", c->connector_id); + } + + return encoder; + } + + unsafe static ModeCrtc* GetCrtc(int fd, ModeEncoder* encoder) + { + ModeCrtc* crtc = (ModeCrtc*)Drm.ModeGetCrtc(fd, encoder->crtc_id); + if (crtc != null) + { + Debug.Print("[KMS] CRTC {0} found for encoder {1}", + encoder->crtc_id, encoder->encoder_id); + } + else + { + Debug.Print("[KMS] Failed to find crtc {0} for encoder {1}", + encoder->crtc_id, encoder->encoder_id); + } + return crtc; + } + + unsafe static void GetModes(LinuxDisplay display, DisplayResolution[] modes, out DisplayResolution current) + { + int mode_count = display.pConnector->count_modes; + Debug.Print("[KMS] Display supports {0} mode(s)", mode_count); + for (int i = 0; i < mode_count; i++) + { + ModeInfo* mode = display.pConnector->modes + i; + if (mode != null) + { + Debug.Print("Mode {0}: {1}x{2} @{3}", i, + mode->hdisplay, mode->vdisplay, mode->vrefresh); + DisplayResolution res = GetDisplayResolution(mode); + modes[i] = res; + } + } + + if (display.pCrtc->mode_valid != 0) + { + ModeInfo cmode = display.pCrtc->mode; + current = GetDisplayResolution(&cmode); + } + else + { + current = GetDisplayResolution(display.pConnector->modes); + } + Debug.Print("Current mode: {0}", current.ToString()); + } + + System.Drawing.Rectangle GetBounds(DisplayResolution current) + { + // Note: since we are not running a display manager, we are free + // to choose the display layout for multiple displays ourselves. + // We choose the simplest layout: displays are laid out side-by-side + // from left to right. Primary display is the first display we encounter. + int x = AvailableDevices.Count == 0 ? + 0 : AvailableDevices[AvailableDevices.Count - 1].Bounds.Right; + int y = 0; + + return new System.Drawing.Rectangle( + x, y, current.Width, current.Height); + } + + void UpdateDisplayIndices(LinuxDisplay display, DisplayDevice device) + { + if (!DisplayIds.ContainsKey(display.Id)) + { + Debug.Print("[KMS] Adding display {0} as {1}", display.Id, AvailableDevices.Count); + DisplayIds.Add(display.Id, AvailableDevices.Count); + } + int index = DisplayIds[display.Id]; + if (index >= AvailableDevices.Count) + { + AvailableDevices.Add(device); + } + else + { + AvailableDevices[index] = device; + } + } + + unsafe static bool QueryDisplay(int fd, ModeConnector* c, out LinuxDisplay display) + { + display = null; + + // Find corresponding encoder + ModeEncoder* encoder = GetEncoder(fd, c); + if (encoder == null) + return false; + + ModeCrtc* crtc = GetCrtc(fd, encoder); + if (crtc == null) + return false; + + display = new LinuxDisplay(fd, (IntPtr)c, (IntPtr)encoder, (IntPtr)crtc); + return true; + } + + unsafe void AddDisplay(LinuxDisplay display) + { + DisplayResolution[] modes = new DisplayResolution[display.pConnector->count_modes]; + DisplayResolution current; + GetModes(display, modes, out current); + + bool is_primary = AvailableDevices.Count == 0; + DisplayDevice device = new DisplayDevice(current, is_primary, + modes, GetBounds(current), display); + + if (is_primary) + { + Primary = device; + } + + UpdateDisplayIndices(display, device); + + Debug.Print("[KMS] Added DisplayDevice {0}", device); + } + + unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode) + { + return new DisplayResolution( + 0, 0, + mode->hdisplay, mode->vdisplay, + 32, // This is actually part of the framebuffer, not the DisplayResolution + mode->vrefresh); + } + + unsafe static ModeInfo* GetModeInfo(LinuxDisplay display, DisplayResolution resolution) + { + for (int i = 0; i < display.pConnector->count_modes; i++) + { + ModeInfo* mode = display.pConnector->modes + i; + if (mode != null && + mode->hdisplay == resolution.Width && + mode->vdisplay == resolution.Height) + { + return mode; + } + } + return null; + } + + #region IDisplayDeviceDriver + + public override bool TryChangeResolution(DisplayDevice device, DisplayResolution resolution) + { + unsafe + { + LinuxDisplay display = (LinuxDisplay)device.Id; + ModeInfo* mode = GetModeInfo(display, resolution); + int connector_id = display.pConnector->connector_id; + if (mode != null) + { + return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0, + &connector_id, 1, mode) == 0; + } + return false; + } + } + + public override bool TryRestoreResolution(DisplayDevice device) + { + unsafe + { + LinuxDisplay display = (LinuxDisplay)device.Id; + ModeInfo mode = display.OriginalMode; + int connector_id = display.pConnector->connector_id; + return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0, + &connector_id, 1, &mode) == 0; + } + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs new file mode 100644 index 00000000..21132c13 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -0,0 +1,248 @@ +#region License +// +// LinuxFactory.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using OpenTK.Graphics; +using OpenTK.Input; +using OpenTK.Platform.Egl; + +namespace OpenTK.Platform.Linux +{ + using Egl = OpenTK.Platform.Egl.Egl; + + // Linux KMS platform + class LinuxFactory : PlatformFactoryBase + { + int _fd; + IntPtr gbm_device; + IntPtr egl_display; + + IJoystickDriver2 JoystickDriver; + LinuxInput MouseKeyboardDriver; + + const string gpu_path = "/dev/dri"; // card0, card1, ... + + public LinuxFactory() + { + Debug.Print("[KMS] Using Linux/KMS backend."); + } + + #region Private Members + + int gpu_fd + { + get + { + lock (this) + { + if (_fd == 0) + { + _fd = CreateDisplay(out gbm_device, out egl_display); + } + return _fd; + } + } + } + + static int CreateDisplay(out IntPtr gbm_device, out IntPtr egl_display) + { + // Query all GPUs until we find one that has a connected display. + // This is necessary in multi-gpu systems, where only one GPU + // can output a signal. + // Todo: allow OpenTK to drive multiple GPUs + // Todo: allow OpenTK to run on an offscreen GPU + // Todo: allow the user to pick a GPU + int fd = 0; + gbm_device = IntPtr.Zero; + egl_display = IntPtr.Zero; + + var files = Directory.GetFiles(gpu_path); + foreach (var gpu in files) + { + if (Path.GetFileName(gpu).StartsWith("card")) + { + int test_fd = SetupDisplay(gpu, out gbm_device, out egl_display); + if (test_fd >= 0) + { + try + { + if (LinuxDisplayDriver.QueryDisplays(test_fd, null)) + { + fd = test_fd; + break; + } + } + catch (Exception e) + { + Debug.WriteLine(e.ToString()); + } + + Debug.Print("[KMS] GPU '{0}' is not connected, skipping.", gpu); + Libc.close(test_fd); + } + } + } + + if (fd == 0) + { + Debug.Print("[Error] No valid GPU found, bailing out."); + throw new PlatformNotSupportedException(); + } + + return fd; + } + + static int SetupDisplay(string gpu, out IntPtr gbm_device, out IntPtr egl_display) + { + Debug.Print("[KMS] Attempting to use gpu '{0}'.", gpu); + + gbm_device = IntPtr.Zero; + egl_display = IntPtr.Zero; + + int fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); + if (fd < 0) + { + Debug.Print("[KMS] Failed to open gpu"); + return fd; + } + Debug.Print("[KMS] GPU '{0}' opened as fd:{1}", gpu, fd); + + gbm_device = Gbm.CreateDevice(fd); + if (gbm_device == IntPtr.Zero) + { + throw new NotSupportedException("[KMS] Failed to create GBM device"); + } + Debug.Print("[KMS] GBM {0:x} created successfully; ", gbm_device); + + egl_display = Egl.GetDisplay(gbm_device); + if (egl_display == IntPtr.Zero) + { + throw new NotSupportedException("[KMS] Failed to create EGL display"); + } + Debug.Print("[KMS] EGL display {0:x} created successfully", egl_display); + + int major, minor; + if (!Egl.Initialize(egl_display, out major, out minor)) + { + ErrorCode error = Egl.GetError(); + throw new NotSupportedException("[KMS] Failed to initialize EGL display. Error code: " + error); + } + Debug.Print("[KMS] EGL {0}.{1} initialized successfully on display {2:x}", major, minor, egl_display); + + return fd; + } + + #endregion + + #region Protected Members + + protected override void Dispose(bool manual) + { + if (egl_display != IntPtr.Zero) + { + Debug.Print("[KMS] Terminating EGL."); + Egl.Terminate(egl_display); + egl_display = IntPtr.Zero; + } + if (gbm_device != IntPtr.Zero) + { + Debug.Print("[KMS] Destroying GBM device."); + Gbm.DestroyDevice(gbm_device); + gbm_device = IntPtr.Zero; + } + if (_fd >= 0) + { + Debug.Print("[KMS] Closing GPU fd."); + Libc.close(_fd); + } + + base.Dispose(manual); + } + + #endregion + + #region IPlatformFactory Members + + public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) + { + return new LinuxNativeWindow(egl_display, gbm_device, gpu_fd, x, y, width, height, title, mode, options, display_device); + } + + public override IDisplayDeviceDriver CreateDisplayDeviceDriver() + { + return new LinuxDisplayDriver(gpu_fd); + } + + public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) + { + return new LinuxGraphicsContext(mode, (LinuxWindowInfo)window, shareContext, major, minor, flags); + } + + public override GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext() + { + return (GraphicsContext.GetCurrentContextDelegate)delegate + { + return new ContextHandle(Egl.GetCurrentContext()); + }; + } + + public override IKeyboardDriver2 CreateKeyboardDriver() + { + lock (this) + { + MouseKeyboardDriver = MouseKeyboardDriver ?? new LinuxInput(); + return MouseKeyboardDriver; + } + } + + public override IMouseDriver2 CreateMouseDriver() + { + lock (this) + { + MouseKeyboardDriver = MouseKeyboardDriver ?? new LinuxInput(); + return MouseKeyboardDriver; + } + } + + public override IJoystickDriver2 CreateJoystickDriver() + { + lock (this) + { + JoystickDriver = JoystickDriver ?? new LinuxJoystick(); + return JoystickDriver; + } + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs new file mode 100644 index 00000000..d6959544 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -0,0 +1,303 @@ +#region License +// +// LinuxGraphicsContext.cs +// +// Author: +// thefiddler +// +// Copyright (c) 2006-2014 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using OpenTK.Graphics; + +namespace OpenTK.Platform.Linux +{ + /// \internal + /// + /// Defines an IGraphicsContext implementation for the Linux KMS framebuffer. + /// For Linux/X11 and other Unix operating systems, use the more generic + /// instead. + /// + /// + /// Note: to display our results, we need to allocate a GBM framebuffer + /// and point the scanout address to that via Drm.ModeSetCrtc. + /// + class LinuxGraphicsContext : Egl.EglUnixContext + { + BufferObject bo, bo_next; + int fd; + bool is_flip_queued; + int swap_interval; + + public LinuxGraphicsContext(GraphicsMode mode, LinuxWindowInfo window, IGraphicsContext sharedContext, + int major, int minor, GraphicsContextFlags flags) + : base(mode, window, sharedContext, major, minor, flags) + { + if (mode.Buffers < 1) + throw new ArgumentException(); + fd = window.FD; + + PageFlip = HandlePageFlip; + PageFlipPtr = Marshal.GetFunctionPointerForDelegate(PageFlip); + } + + public override void SwapBuffers() + { + base.SwapBuffers(); + + bo_next = LockSurface(); + int fb = GetFramebuffer(bo_next); + + if (is_flip_queued) + { + // Todo: if we don't wait for the page flip, + // we drop all rendering buffers and get a crash + // in Egl.SwapBuffers(). We need to fix that + // before we can disable vsync. + WaitFlip(true); // WaitFlip(SwapInterval > 0) + if (is_flip_queued) + { + Debug.Print("[KMS] Dropping frame"); + return; + } + } + + QueueFlip(fb); + } + + public override void Update(IWindowInfo window) + { + WaitFlip(true); + + base.SwapBuffers(); + + bo = LockSurface(); + int fb = GetFramebuffer(bo); + SetScanoutRegion(fb); + } + + public override int SwapInterval + { + get + { + return swap_interval; + } + set + { + // We only support a SwapInterval of 0 (immediate) + // or 1 (vsynced). + // Todo: add support for SwapInterval of -1 (adaptive). + // This requires a small change in WaitFlip(). + swap_interval = MathHelper.Clamp(value, 0, 1); + } + } + + void WaitFlip(bool block) + { + PollFD fds = new PollFD(); + fds.fd = fd; + fds.events = PollFlags.In; + + EventContext evctx = new EventContext(); + evctx.version = EventContext.Version; + evctx.page_flip_handler = PageFlipPtr; + + int timeout = block ? -1 : 0; + + while (is_flip_queued) + { + fds.revents = 0; + if (Libc.poll(ref fds, 1, timeout) < 0) + break; + + if ((fds.revents & (PollFlags.Hup | PollFlags.Error)) != 0) + break; + + if ((fds.revents & PollFlags.In) != 0) + Drm.HandleEvent(fd, ref evctx); + else + break; + } + + // Page flip has taken place, update buffer objects + if (!is_flip_queued) + { + IntPtr gbm_surface = WindowInfo.Handle; + Gbm.ReleaseBuffer(gbm_surface, bo); + bo = bo_next; + } + } + + void QueueFlip(int buffer) + { + LinuxWindowInfo wnd = WindowInfo as LinuxWindowInfo; + if (wnd == null) + throw new InvalidOperationException(); + + unsafe + { + int ret = Drm.ModePageFlip(fd, wnd.DisplayDevice.Id, buffer, + PageFlipFlags.FlipEvent, IntPtr.Zero); + + if (ret < 0) + { + Debug.Print("[KMS] Failed to enqueue framebuffer flip. Error: {0}", ret); + } + + is_flip_queued = true; + } + } + + void SetScanoutRegion(int buffer) + { + LinuxWindowInfo wnd = WindowInfo as LinuxWindowInfo; + if (wnd == null) + throw new InvalidOperationException(); + + unsafe + { + ModeInfo* mode = wnd.DisplayDevice.pConnector->modes; + int connector_id = wnd.DisplayDevice.pConnector->connector_id; + int crtc_id = wnd.DisplayDevice.Id; + + int x = 0; + int y = 0; + int connector_count = 1; + int ret = Drm.ModeSetCrtc(fd, crtc_id, buffer, x, y, + &connector_id, connector_count, mode); + + if (ret != 0) + { + Debug.Print("[KMS] Drm.ModeSetCrtc{0}, {1}, {2}, {3}, {4:x}, {5}, {6:x}) failed. Error: {7}", + fd, crtc_id, buffer, x, y, (IntPtr)connector_id, connector_count, (IntPtr)mode, ret); + } + } + } + + BufferObject LockSurface() + { + IntPtr gbm_surface = WindowInfo.Handle; + return Gbm.LockFrontBuffer(gbm_surface); + } + + int GetFramebuffer(BufferObject bo) + { + if (bo == BufferObject.Zero) + goto fail; + + int bo_handle = bo.Handle; + if (bo_handle == 0) + { + Debug.Print("[KMS] Gbm.BOGetHandle({0:x}) failed.", bo); + goto fail; + } + + int width = bo.Width; + int height = bo.Height; + int bpp = Mode.ColorFormat.BitsPerPixel; + int depth = Mode.Depth; + int stride = bo.Stride; + + if (width == 0 || height == 0 || bpp == 0) + { + Debug.Print("[KMS] Invalid framebuffer format: {0}x{1} {2} {3} {4}", + width, height, stride, bpp, depth); + goto fail; + } + + int buffer; + int ret = Drm.ModeAddFB( + fd, width, height, + (byte)depth, (byte)bpp, stride, bo_handle, + out buffer); + if (ret != 0) + { + Debug.Print("[KMS] Drm.ModeAddFB({0}, {1}, {2}, {3}, {4}, {5}, {6}) failed. Error: {7}", + fd, width, height, depth, bpp, stride, bo_handle, ret); + goto fail; + } + + bo.SetUserData((IntPtr)buffer, DestroyFB); + return buffer; + + fail: + Debug.Print("[Error] Failed to create framebuffer."); + return -1; + } + + readonly IntPtr PageFlipPtr; + readonly PageFlipCallback PageFlip; + void HandlePageFlip(int fd, + int sequence, + int tv_sec, + int tv_usec, + IntPtr user_data) + { + is_flip_queued = false; + } + + static readonly DestroyUserDataCallback DestroyFB = HandleDestroyFB; + static void HandleDestroyFB(BufferObject bo, IntPtr data) + { + IntPtr gbm = bo.Device; + int fb = data.ToInt32(); + Debug.Print("[KMS] Destroying framebuffer {0}", fb); + + if (fb != 0) + { + Drm.ModeRmFB(Gbm.DeviceGetFD(gbm), fb); + } + } + + protected override void Dispose(bool manual) + { + if (manual) + { + // Reset the scanout region + LinuxWindowInfo wnd = WindowInfo as LinuxWindowInfo; + if (wnd != null) + { + unsafe + { + int connector_id = wnd.DisplayDevice.pConnector->connector_id; + ModeInfo mode = wnd.DisplayDevice.OriginalMode; + Drm.ModeSetCrtc(fd, + wnd.DisplayDevice.pCrtc->crtc_id, + wnd.DisplayDevice.pCrtc->buffer_id, + wnd.DisplayDevice.pCrtc->x, + wnd.DisplayDevice.pCrtc->y, + &connector_id, + 1, + &mode); + } + } + } + base.Dispose(manual); + } + } +} + + + diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs new file mode 100644 index 00000000..ddca85f5 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -0,0 +1,717 @@ +#region License +// +// LinuxKeyboardLibInput.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading; +using OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + class LinuxInput : IKeyboardDriver2, IMouseDriver2, IDisposable + { + class DeviceBase + { + readonly IntPtr Device; + string name; + string output; + string logical_seat; + string physical_seat; + + public DeviceBase(IntPtr device, int id) + { + Device = device; + Id = id; + } + + public int Id + { + get + { + return GetId(Device); + } + set + { + LibInput.DeviceSetData(Device, (IntPtr)value); + } + } + + public string Name + { + get + { + name = name ?? LibInput.DeviceGetName(Device); + return name; + } + } + + public IntPtr Seat + { + get + { + return LibInput.DeviceGetSeat(Device); + } + } + + public string LogicalSeatName + { + get + { + logical_seat = logical_seat ?? LibInput.SeatGetLogicalName(Seat); + return logical_seat; + } + } + + public string PhysicalSeatName + { + get + { + physical_seat = physical_seat ?? LibInput.SeatGetPhysicalName(Seat); + return physical_seat; + } + } + + public string Output + { + get + { + output = output ?? LibInput.DeviceGetOutputName(Device); + return output; + } + } + } + + class KeyboardDevice : DeviceBase + { + public KeyboardState State; + + public KeyboardDevice(IntPtr device, int id) + : base(device, id) + { + } + } + + class MouseDevice : DeviceBase + { + public MouseState State; + + public MouseDevice(IntPtr device, int id) + : base(device, id) + { + } + } + + static readonly object Sync = new object(); + static readonly Key[] KeyMap = Evdev.KeyMap; + static long DeviceFDCount; + + // libinput returns various devices with keyboard/pointer even though + // they are not traditional keyboards/mice (for example "Integrated Camera" + // can be detected as a keyboard.) + // Since there is no API to retrieve actual device capabilities, + // we add all detected devices to a "candidate" list and promote them + // to an actual keyboard/mouse only when we receive a valid input event. + // This is far from optimal, but it appears to be the only viable solution + // unless a new API is added to libinput. + DeviceCollection KeyboardCandidates = new DeviceCollection(); + DeviceCollection MouseCandidates = new DeviceCollection(); + DeviceCollection Keyboards = new DeviceCollection(); + DeviceCollection Mice = new DeviceCollection(); + + // Todo: do we need to maintain the geometry of each display separately? + Rectangle bounds; + + // Global mouse cursor state + Vector2 CursorPosition = Vector2.Zero; + // Global mouse cursor offset (used for emulating SetPosition) + Vector2 CursorOffset = Vector2.Zero; + + IntPtr udev; + IntPtr input_context; + InputInterface input_interface = new InputInterface( + OpenRestricted, CloseRestricted); + int fd; + Thread input_thread; + long exit; + + public LinuxInput() + { + Debug.Print("[Linux] Initializing {0}", GetType().Name); + Debug.Indent(); + try + { + Semaphore ready = new Semaphore(0, 1); + input_thread = new Thread(InputThreadLoop); + input_thread.IsBackground = true; + input_thread.Start(ready); + + // Wait until the input thread is ready. + // Note: it would be nicer if we could avoid this. + // however we need to marshal errors back to the caller + // as exceptions. + // Todo: in a future version, we should add an "Application" object + // to handle all communication with the OS (including event processing.) + // Once we do that, we can remove all separate input threads. + ready.WaitOne(); + if (exit != 0) + { + throw new NotSupportedException(); + } + } + finally + { + Debug.Print("Initialization {0}", exit == 0 ? + "complete" : "failed"); + Debug.Unindent(); + } + } + + #region Private Members + + static CloseRestrictedCallback CloseRestricted = CloseRestrictedHandler; + static void CloseRestrictedHandler(int fd, IntPtr data) + { + Debug.Print("[Input] Closing fd {0}", fd); + int ret = Libc.close(fd); + + if (ret < 0) + { + Debug.Print("[Input] Failed to close fd {0}. Error: {1}", fd, ret); + } + else + { + Interlocked.Decrement(ref DeviceFDCount); + } + } + + static OpenRestrictedCallback OpenRestricted = OpenRestrictedHandler; + static int OpenRestrictedHandler(IntPtr path, int flags, IntPtr data) + { + int fd = Libc.open(path, (OpenFlags)flags); + Debug.Print("[Input] Opening '{0}' with flags {1}. fd:{2}", + Marshal.PtrToStringAnsi(path), (OpenFlags)flags, fd); + + if (fd >= 0) + { + Interlocked.Increment(ref DeviceFDCount); + } + + return fd; + } + + void InputThreadLoop(object semaphore) + { + Debug.Print("[Input] Running on thread {0}", Thread.CurrentThread.ManagedThreadId); + Setup(); + + // Inform the parent thread that initialization has completed successfully + (semaphore as Semaphore).Release(); + Debug.Print("[Input] Released main thread.", input_context); + + // Use a blocking poll for input messages, in order to reduce CPU usage + PollFD poll_fd = new PollFD(); + poll_fd.fd = fd; + poll_fd.events = PollFlags.In; + Debug.Print("[Input] Created PollFD({0}, {1})", poll_fd.fd, poll_fd.events); + + Debug.Print("[Input] Entering input loop.", poll_fd.fd, poll_fd.events); + while (Interlocked.Read(ref exit) == 0) + { + int ret = Libc.poll(ref poll_fd, 1, -1); + ErrorNumber error = (ErrorNumber)Marshal.GetLastWin32Error(); + bool is_error = + ret < 0 && !(error == ErrorNumber.Again || error == ErrorNumber.Interrupted) || + (poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0; + + // We need to query the desktop bounds in order to position the mouse cursor correctly. + // This value will be used for the current bunch of input events. If a monitor changes + // resolution in the meantime, we might be slightly off in our calculations - this error + // will be corrected when the next bunch of input events arrives. + UpdateDisplayBounds(); + + if (ret > 0 && (poll_fd.revents & (PollFlags.In | PollFlags.Pri)) != 0) + { + ProcessEvents(input_context); + } + + if (is_error) + { + Debug.Print("[Input] Exiting input loop {0} due to poll error [ret:{1} events:{2}]. Error: {3}.", + input_thread.ManagedThreadId, ret, poll_fd.revents, error); + Interlocked.Increment(ref exit); + } + } + Debug.Print("[Input] Exited input loop.", poll_fd.fd, poll_fd.events); + } + + void UpdateDisplayBounds() + { + bounds = Rectangle.Empty; + for (DisplayIndex i = DisplayIndex.First; i < DisplayIndex.Sixth; i++) + { + DisplayDevice display = DisplayDevice.GetDisplay(i); + if (display != null) + { + bounds = Rectangle.Union(bounds, display.Bounds); + } + } + } + + void UpdateCursor() + { + Point p = new Point( + (int)Math.Round(CursorPosition.X + CursorOffset.X), + (int)Math.Round(CursorPosition.Y + CursorOffset.Y)); + + DisplayDevice display = DisplayDevice.FromPoint(p.X, p.Y) ?? DisplayDevice.Default; + if (display != null) + { + LinuxDisplay d = (LinuxDisplay)display.Id; + Drm.MoveCursor(d.FD, d.Id, p.X, p.Y); + } + } + + void Setup() + { + // Todo: add static path fallback when udev is not installed. + udev = Udev.New(); + if (udev == IntPtr.Zero) + { + Debug.Print("[Input] Udev.New() failed."); + Interlocked.Increment(ref exit); + return; + } + Debug.Print("[Input] Udev.New() = {0:x}", udev); + + input_context = LibInput.CreateContext(input_interface, IntPtr.Zero, udev, "seat0"); + if (input_context == IntPtr.Zero) + { + Debug.Print("[Input] LibInput.CreateContext({0:x}) failed.", udev); + Interlocked.Increment(ref exit); + return; + } + Debug.Print("[Input] LibInput.CreateContext({0:x}) = {1:x}", udev, input_context); + + fd = LibInput.GetFD(input_context); + if (fd < 0) + { + Debug.Print("[Input] LibInput.GetFD({0:x}) failed.", input_context); + Interlocked.Increment(ref exit); + return; + } + Debug.Print("[Input] LibInput.GetFD({0:x}) = {1}.", input_context, fd); + + ProcessEvents(input_context); + LibInput.Resume(input_context); + Debug.Print("[Input] LibInput.Resume({0:x})", input_context); + + if (Interlocked.Read(ref DeviceFDCount) <= 0) + { + Debug.Print("[Error] Failed to open any input devices."); + Debug.Print("[Error] Ensure that you have access to '/dev/input/event*'."); + Interlocked.Increment(ref exit); + } + } + + void ProcessEvents(IntPtr input_context) + { + // Process all events in the event queue + while (true) + { + // Data available + int ret = LibInput.Dispatch(input_context); + if (ret != 0) + { + Debug.Print("[Input] LibInput.Dispatch({0:x}) failed. Error: {1}", + input_context, ret); + break; + } + + IntPtr pevent = LibInput.GetEvent(input_context); + if (pevent == IntPtr.Zero) + { + break; + } + + IntPtr device = LibInput.GetDevice(pevent); + InputEventType type = LibInput.GetEventType(pevent); + + lock (Sync) + { + switch (type) + { + case InputEventType.DeviceAdded: + HandleDeviceAdded(input_context, device); + break; + + case InputEventType.DeviceRemoved: + HandleDeviceRemoved(input_context, device); + break; + + case InputEventType.KeyboardKey: + HandleKeyboard(GetKeyboard(device), LibInput.GetKeyboardEvent(pevent)); + break; + + case InputEventType.PointerAxis: + HandlePointerAxis(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + + case InputEventType.PointerButton: + HandlePointerButton(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + + case InputEventType.PointerMotion: + HandlePointerMotion(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + + case InputEventType.PointerMotionAbsolute: + HandlePointerMotionAbsolute(GetMouse(device), LibInput.GetPointerEvent(pevent)); + break; + } + } + + LibInput.DestroyEvent(pevent); + } + } + + void HandleDeviceAdded(IntPtr context, IntPtr device) + { + if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) + { + KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); + KeyboardCandidates.Add(keyboard.Id, keyboard); + Debug.Print("[Input] Added keyboard device {0} '{1}' on '{2}' ('{3}')", + keyboard.Id, keyboard.Name, keyboard.LogicalSeatName, keyboard.PhysicalSeatName); + } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) + { + MouseDevice mouse = new MouseDevice(device, Mice.Count); + MouseCandidates.Add(mouse.Id, mouse); + Debug.Print("[Input] Added mouse device {0} '{1}' on '{2}' ('{3}')", + mouse.Id, mouse.Name, mouse.LogicalSeatName, mouse.PhysicalSeatName); + } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Touch)) + { + Debug.Print("[Input] Todo: touch device."); + } + } + + void HandleDeviceRemoved(IntPtr context, IntPtr device) + { + if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) + { + int id = GetId(device); + Keyboards.TryRemove(id); + KeyboardCandidates.TryRemove(id); + } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) + { + int id = GetId(device); + Mice.TryRemove(id); + MouseCandidates.TryRemove(id); + } + } + + void HandleKeyboard(KeyboardDevice device, KeyboardEvent e) + { + if (device != null) + { + device.State.SetIsConnected(true); + Debug.Print("[Input] Added keyboard {0}", device.Id); + + Key key = Key.Unknown; + uint raw = e.Key; + if (raw >= 0 && raw < KeyMap.Length) + { + key = KeyMap[raw]; + } + + if (key == Key.Unknown) + { + Debug.Print("[Linux] Unknown key with code '{0}'", raw); + } + + device.State.SetKeyState(key, e.KeyState == KeyState.Pressed); + } + } + + void HandlePointerAxis(MouseDevice mouse, PointerEvent e) + { + if (mouse != null) + { + mouse.State.SetIsConnected(true); + + double value = e.AxisValue; + PointerAxis axis = e.Axis; + switch (axis) + { + case PointerAxis.HorizontalScroll: + mouse.State.SetScrollRelative((float)value, 0); + break; + + case PointerAxis.VerticalScroll: + mouse.State.SetScrollRelative(0, (float)value); + break; + + default: + Debug.Print("[Input] Unknown scroll axis {0}.", axis); + break; + } + } + } + + void HandlePointerButton(MouseDevice mouse, PointerEvent e) + { + if (mouse != null) + { + mouse.State.SetIsConnected(true); + + MouseButton button = Evdev.GetMouseButton(e.Button); + ButtonState state = e.ButtonState; + mouse.State[(MouseButton)button] = state == ButtonState.Pressed; + } + } + + void HandlePointerMotion(MouseDevice mouse, PointerEvent e) + { + Vector2 delta = new Vector2((float)e.X, (float)e.Y); + if (mouse != null) + { + mouse.State.SetIsConnected(true); + mouse.State.Position += delta; + } + + CursorPosition = new Vector2( + MathHelper.Clamp(CursorPosition.X + delta.X, bounds.Left, bounds.Right - 1), + MathHelper.Clamp(CursorPosition.Y + delta.Y, bounds.Top, bounds.Bottom - 1)); + UpdateCursor(); + } + + void HandlePointerMotionAbsolute(MouseDevice mouse, PointerEvent e) + { + if (mouse != null) + { + mouse.State.SetIsConnected(true); + mouse.State.Position = new Vector2(e.X, e.Y); + } + + CursorPosition = new Vector2( + e.TransformedX(bounds.Width), + e.TransformedY(bounds.Height)); + UpdateCursor(); + } + + static int GetId(IntPtr device) + { + return LibInput.DeviceGetData(device).ToInt32(); + } + + KeyboardDevice GetKeyboard(IntPtr device) + { + int id = GetId(device); + KeyboardDevice keyboard = KeyboardCandidates.FromHardwareId(id); + if (keyboard != null) + { + Keyboards.Add(id, keyboard); + } + else + { + Debug.Print("[Input] Keyboard {0} does not exist in device list.", id); + } + return keyboard; + } + + MouseDevice GetMouse(IntPtr device) + { + int id = GetId(device); + MouseDevice mouse = MouseCandidates.FromHardwareId(id); + if (mouse != null) + { + Mice.Add(id, mouse); + } + else + { + Debug.Print("[Input] Mouse {0} does not exist in device list.", id); + } + return mouse; + } + + #endregion + + #region IKeyboardDriver2 implementation + + KeyboardState IKeyboardDriver2.GetState() + { + lock (Sync) + { + KeyboardState state = new KeyboardState(); + foreach (KeyboardDevice keyboard in Keyboards) + { + state.MergeBits(keyboard.State); + } + return state; + } + } + + KeyboardState IKeyboardDriver2.GetState(int index) + { + lock (Sync) + { + KeyboardDevice device = Keyboards.FromIndex(index); + if (device != null) + { + return device.State; + } + else + { + return new KeyboardState(); + } + } + } + + string IKeyboardDriver2.GetDeviceName(int index) + { + lock (Sync) + { + KeyboardDevice device = Keyboards.FromIndex(index); + if (device != null) + { + return device.Name; + } + else + { + return String.Empty; + } + } + } + + #endregion + + #region IMouseDriver2 implementation + + MouseState IMouseDriver2.GetState() + { + lock (Sync) + { + MouseState state = new MouseState(); + foreach (MouseDevice mouse in Mice) + { + state.MergeBits(mouse.State); + } + return state; + } + } + + MouseState IMouseDriver2.GetState(int index) + { + lock (Sync) + { + MouseDevice device = Mice.FromIndex(index); + if (device != null) + { + return device.State; + } + else + { + return new MouseState(); + } + } + } + + void IMouseDriver2.SetPosition(double x, double y) + { + // Todo: this does not appear to be supported in libinput. + // We will have to emulate this in the KMS mouse rendering code. + CursorOffset = new Vector2( + (float)x - CursorPosition.X, + (float)y - CursorPosition.Y); + UpdateCursor(); + } + + MouseState IMouseDriver2.GetCursorState() + { + MouseState state = (this as IMouseDriver2).GetState(); + state.Position = CursorPosition + CursorOffset; + return state; + } + + #endregion + + #region IDisposable implementation + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposing) + { + if (input_context != IntPtr.Zero) + { + Debug.Print("[Input] Destroying libinput context"); + LibInput.Suspend(input_context); + Interlocked.Increment(ref exit); + + LibInput.DestroyContext(input_context); + input_context = IntPtr.Zero; + } + + if (udev != IntPtr.Zero) + { + Debug.Print("[Input] Destroying udev context"); + Udev.Destroy(udev); + udev = IntPtr.Zero; + } + + input_interface = null; + } + else + { + Debug.Print("[Input] {0} leaked. Did you forget to call Dispose()?", GetType().FullName); + } + } + + ~LinuxInput() + { + Dispose(false); + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/X11/X11Joystick.cs b/Source/OpenTK/Platform/Linux/LinuxJoystick.cs similarity index 75% rename from Source/OpenTK/Platform/X11/X11Joystick.cs rename to Source/OpenTK/Platform/Linux/LinuxJoystick.cs index 4b3fdc8c..51992797 100644 --- a/Source/OpenTK/Platform/X11/X11Joystick.cs +++ b/Source/OpenTK/Platform/Linux/LinuxJoystick.cs @@ -33,9 +33,9 @@ using System.Runtime.InteropServices; using System.Text; using OpenTK.Input; -namespace OpenTK.Platform.X11 +namespace OpenTK.Platform.Linux { - struct X11JoyDetails + struct LinuxJoyDetails { public Guid Guid; public int FileDescriptor; @@ -43,7 +43,7 @@ namespace OpenTK.Platform.X11 } // Note: despite what the name says, this class is Linux-specific. - sealed class X11Joystick : IJoystickDriver2 + sealed class LinuxJoystick : IJoystickDriver2 { #region Fields @@ -52,7 +52,7 @@ namespace OpenTK.Platform.X11 readonly FileSystemWatcher watcher = new FileSystemWatcher(); readonly Dictionary index_to_stick = new Dictionary(); - List> sticks = new List>(); + List> sticks = new List>(); bool disposed; @@ -60,7 +60,7 @@ namespace OpenTK.Platform.X11 #region Constructors - public X11Joystick() + public LinuxJoystick() { string path = Directory.Exists(JoystickPath) ? JoystickPath : @@ -89,7 +89,7 @@ namespace OpenTK.Platform.X11 { foreach (string file in Directory.GetFiles(path)) { - JoystickDevice stick = OpenJoystick(file); + JoystickDevice stick = OpenJoystick(file); if (stick != null) { //stick.Description = String.Format("USB Joystick {0} ({1} axes, {2} buttons, {3}{0})", @@ -155,7 +155,7 @@ namespace OpenTK.Platform.X11 #region Private Members - Guid CreateGuid(JoystickDevice js, string path, int number) + Guid CreateGuid(JoystickDevice js, string path, int number) { byte[] bytes = new byte[16]; for (int i = 0; i < Math.Min(bytes.Length, js.Description.Length); i++) @@ -221,9 +221,9 @@ namespace OpenTK.Platform.X11 #endif } - JoystickDevice OpenJoystick(string path) + JoystickDevice OpenJoystick(string path) { - JoystickDevice stick = null; + JoystickDevice stick = null; int number = GetJoystickNumber(Path.GetFileName(path)); if (number >= 0) @@ -231,28 +231,28 @@ namespace OpenTK.Platform.X11 int fd = -1; try { - fd = UnsafeNativeMethods.open(path, OpenFlags.NonBlock); + fd = Libc.open(path, OpenFlags.NonBlock); if (fd == -1) return null; // Check joystick driver version (must be 1.0+) int driver_version = 0x00000800; - UnsafeNativeMethods.ioctl(fd, JoystickIoctlCode.Version, ref driver_version); + Libc.ioctl(fd, JoystickIoctlCode.Version, ref driver_version); if (driver_version < 0x00010000) return null; // Get number of joystick axes int axes = 0; - UnsafeNativeMethods.ioctl(fd, JoystickIoctlCode.Axes, ref axes); + Libc.ioctl(fd, JoystickIoctlCode.Axes, ref axes); // Get number of joystick buttons int buttons = 0; - UnsafeNativeMethods.ioctl(fd, JoystickIoctlCode.Buttons, ref buttons); + Libc.ioctl(fd, JoystickIoctlCode.Buttons, ref buttons); - stick = new JoystickDevice(number, axes, buttons); + stick = new JoystickDevice(number, axes, buttons); StringBuilder sb = new StringBuilder(128); - UnsafeNativeMethods.ioctl(fd, JoystickIoctlCode.Name128, sb); + Libc.ioctl(fd, JoystickIoctlCode.Name128, sb); stick.Description = sb.ToString(); stick.Details.FileDescriptor = fd; @@ -287,16 +287,16 @@ namespace OpenTK.Platform.X11 finally { if (stick == null && fd != -1) - UnsafeNativeMethods.close(fd); + Libc.close(fd); } } return stick; } - void CloseJoystick(JoystickDevice js) + void CloseJoystick(JoystickDevice js) { - UnsafeNativeMethods.close(js.Details.FileDescriptor); + Libc.close(js.Details.FileDescriptor); js.Details.State = new JoystickState(); // clear joystick state js.Details.FileDescriptor = -1; @@ -317,13 +317,13 @@ namespace OpenTK.Platform.X11 } } - void PollJoystick(JoystickDevice js) + void PollJoystick(JoystickDevice js) { JoystickEvent e; unsafe { - while ((long)UnsafeNativeMethods.read(js.Details.FileDescriptor, (void*)&e, (UIntPtr)sizeof(JoystickEvent)) > 0) + while ((long)Libc.read(js.Details.FileDescriptor, (void*)&e, (UIntPtr)sizeof(JoystickEvent)) > 0) { e.Type &= ~JoystickEventType.Init; @@ -352,78 +352,9 @@ namespace OpenTK.Platform.X11 return index_to_stick.ContainsKey(index); } - #region UnsafeNativeMethods - - struct EvdevInputId - { - public ushort BusType; - public ushort Vendor; - public ushort Product; - public ushort Version; - } - - enum EvdevIoctlCode : uint - { - Id = ((byte)'E' << 8) | (0x02 << 0) //EVIOCGID, which is _IOR('E', 0x02, struct input_id) - } - - - struct JoystickEvent - { - public uint Time; // (u32) event timestamp in milliseconds - public short Value; // (s16) value - public JoystickEventType Type; // (u8) event type - public byte Number; // (u8) axis/button number - } - - [Flags] - enum JoystickEventType : byte - { - Button = 0x01, // button pressed/released - Axis = 0x02, // joystick moved - Init = 0x80 // initial state of device - } - - enum JoystickIoctlCode : uint - { - Version = 0x80046a01, - Axes = 0x80016a11, - Buttons = 0x80016a12, - Name128 = (2u << 30) | (0x6A << 8) | (0x13 << 0) | (128 << 16) //JSIOCGNAME(128), which is _IOC(_IO_READ, 'j', 0x13, len) - } - static readonly string JoystickPath = "/dev/input"; static readonly string JoystickPathLegacy = "/dev"; - [Flags] - enum OpenFlags - { - NonBlock = 0x00000800 - } - - static class UnsafeNativeMethods - { - [DllImport("libc", SetLastError = true)] - public static extern int ioctl(int d, JoystickIoctlCode request, ref int data); - - [DllImport("libc", SetLastError = true)] - public static extern int ioctl(int d, JoystickIoctlCode request, StringBuilder data); - - [DllImport("libc", SetLastError = true)] - public static extern int ioctl(int d, EvdevIoctlCode request, out EvdevInputId data); - - [DllImport("libc", SetLastError = true)] - public static extern int open([MarshalAs(UnmanagedType.LPStr)]string pathname, OpenFlags flags); - - [DllImport("libc", SetLastError = true)] - public static extern int close(int fd); - - [DllImport("libc", SetLastError = true)] - unsafe public static extern IntPtr read(int fd, void* buffer, UIntPtr count); - } - - #endregion - #endregion #region IDisposable Members @@ -443,7 +374,7 @@ namespace OpenTK.Platform.X11 } watcher.Dispose(); - foreach (JoystickDevice js in sticks) + foreach (JoystickDevice js in sticks) { CloseJoystick(js); } @@ -452,7 +383,7 @@ namespace OpenTK.Platform.X11 } } - ~X11Joystick() + ~LinuxJoystick() { Dispose(false); } @@ -465,7 +396,7 @@ namespace OpenTK.Platform.X11 { if (IsValid(index)) { - JoystickDevice js = + JoystickDevice js = sticks[index_to_stick[index]]; PollJoystick(js); return js.Details.State; @@ -478,7 +409,7 @@ namespace OpenTK.Platform.X11 JoystickCapabilities caps = new JoystickCapabilities(); if (IsValid(index)) { - JoystickDevice js = sticks[index_to_stick[index]]; + JoystickDevice js = sticks[index_to_stick[index]]; caps = new JoystickCapabilities( js.Axis.Count, js.Button.Count, @@ -492,7 +423,7 @@ namespace OpenTK.Platform.X11 { if (IsValid(index)) { - JoystickDevice js = sticks[index_to_stick[index]]; + JoystickDevice js = sticks[index_to_stick[index]]; return js.Details.Guid; } return new Guid(); diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs new file mode 100644 index 00000000..afe66bc1 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs @@ -0,0 +1,268 @@ +#region License +// +// LinuxKeyboardTTY.cs +// +// Author: +// thefiddler +// +// Copyright (c) 2006-2014 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + // Todo: this has terrible side-effects on process exit + // (the keyboard remains tied up.) We need to find a + // proper way to clean up after ourselves, even in case + // of a crash. + #if EXPERIMENTAL + class LinuxKeyboardTTY : IKeyboardDriver2, IDisposable + { + const int stdin = 0; // STDIN_FILENO + readonly object sync = new object(); + Thread input_thread; + long exit; + KeyboardState state; + + TerminalState original_state; + TerminalState current_state; + + IntPtr original_mode = new IntPtr(-1); + int original_stdin; + + public LinuxKeyboardTTY() + { + Debug.Print("[Linux] Using TTY keyboard input."); + + if (!SetupTTY(stdin)) + { + throw new NotSupportedException(); + } + + input_thread = new Thread(ProcessEvents); + input_thread.IsBackground = true; + input_thread.Start(); + } + + #region Private Members + + bool SetupTTY(int stdin) + { + // Ensure that we are using a real terminal, + // rather than some short of file redirection.thing. + if (!Terminal.IsTerminal(stdin)) + { + Debug.Print("[Linux] Terminal.IsTerminal({0}) returned false.", stdin); + return false; + } + + //original_stdin = Libc.dup(stdin); + + int ret = Terminal.GetAttributes(stdin, out original_state); + if (ret < 0) + { + Debug.Print("[Linux] Terminal.GetAttributes({0}) failed. Error: {1}", + stdin, ret); + return false; + } + + // Retrieve current keyboard mode + ret = Libc.ioctl(stdin, KeyboardIoctlCode.GetMode, ref original_mode); + if (ret != 0) + { + Debug.Print("[Linux] Libc.ioctl({0}, KeyboardIoctlCode.GetMode) failed. Error: {1}", + stdin, ret); + return false; + } + + // Update terminal state + current_state = original_state; + current_state.LocalMode &= ~(/*LocalFlags.ECHO |*/ LocalFlags.ICANON | LocalFlags.ISIG); + current_state.InputMode &= ~( + InputFlags.ISTRIP | InputFlags.IGNCR | InputFlags.ICRNL | + InputFlags.INLCR | InputFlags.IXOFF | InputFlags.IXON); + current_state.ControlCharacters.VMIN = 0; + current_state.ControlCharacters.VTIME = 0; + Terminal.SetAttributes(stdin, OptionalActions.FLUSH, ref current_state); + + // Request keycodes + int mode = 0x02; // K_MEDIUMRAW + ret = Libc.ioctl(stdin, KeyboardIoctlCode.SetMode, mode); + if (ret != 0) + { + Debug.Print("[Linux] Libc.ioctl({0}, KeyboardIoctlCode.SetMode, {1}) failed. Error: {2}", + stdin, mode, ret); + ExitTTY(this, EventArgs.Empty); + return false; + } + + // Ensure we reset the original keyboard/terminal state on exit, + // even if we crash. + HookEvents(); + + return true; + } + + void ExitTTY(object sender, EventArgs e) + { + if (original_mode != new IntPtr(-1)) + { + Debug.Print("[Linux] Exiting TTY keyboard input."); + + Libc.ioctl(stdin, KeyboardIoctlCode.SetMode, ref original_mode); + Terminal.SetAttributes(stdin, OptionalActions.FLUSH, ref original_state); + original_mode = new IntPtr(-1); + + UnhookEvents(); + } + } + + void HookEvents() + { + Process.GetCurrentProcess().Exited += ExitTTY; + Console.CancelKeyPress += ExitTTY; + } + + void UnhookEvents() + { + Process.GetCurrentProcess().Exited -= ExitTTY; + Console.CancelKeyPress -= ExitTTY; + } + + void ProcessEvents() + { + state.SetIsConnected(true); + + while (Interlocked.Read(ref exit) == 0) + { + byte scancode; + short extended; + + while (Libc.read(stdin, out scancode) > 0) + { + bool pressed = (scancode & 0x80) == 0; + int key = scancode & ~0x80; + KeyModifiers mods; + Debug.Print("{0}:{1} is {2}", key, (int)TranslateKey(key, out mods), pressed); + + if (key == 0) + { + // This is an extended scancode, ignore + Libc.read(stdin, out extended); + } + else + { + lock (sync) + { + state[(Key)key] = pressed; + } + } + + } + } + + input_thread = null; + } + + Key TranslateKey(int key, out KeyModifiers mods) + { + int k = MathHelper.Clamp((int)key, 0, KeyMap.Length); + Key result = KeyMap[k]; + mods = 0; + mods |= (result == Key.AltLeft || result == Key.AltRight) ? KeyModifiers.Alt : 0; + mods |= (result == Key.ControlLeft || result == Key.ControlRight) ? KeyModifiers.Control : 0; + mods |= (result == Key.ShiftLeft || result == Key.ShiftRight) ? KeyModifiers.Shift : 0; + return KeyMap[k]; + } + + static readonly Key[] KeyMap = Evdev.KeyMap; + + #endregion + + #region IKeyboardDriver2 implementation + + public KeyboardState GetState() + { + lock (this) + { + return state; + } + } + + public KeyboardState GetState(int index) + { + lock (this) + { + if (index == 0) + return state; + else + return new KeyboardState(); + } + } + + public string GetDeviceName(int index) + { + if (index == 0) + return "Standard Input"; + else + return String.Empty; + } + + #endregion + + #region IDisposable Implementation + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + Interlocked.Increment(ref exit); + + if (disposing) + { + ExitTTY(this, EventArgs.Empty); + } + else + { + Debug.Print("{0} leaked, did you forget to call Dispose()?", typeof(LinuxKeyboardTTY).FullName); + } + } + + ~LinuxKeyboardTTY() + { + Dispose(false); + } + + #endregion + } + #endif +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs new file mode 100644 index 00000000..961381ee --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -0,0 +1,549 @@ +#region License +// +// LinuxNativeWindow.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using OpenTK.Graphics; +using OpenTK.Input; +using OpenTK.Platform.Egl; + +namespace OpenTK.Platform.Linux +{ + using Egl = OpenTK.Platform.Egl.Egl; + + class LinuxNativeWindow : NativeWindowBase + { + LinuxWindowInfo window; + string title; + Icon icon; + Rectangle bounds; + Size client_size; + bool exists; + bool is_focused; + bool is_cursor_visible = true; + + KeyboardState previous_keyboard; + MouseState previous_mouse; + + MouseCursor cursor_current; + BufferObject cursor_custom; + BufferObject cursor_default; + BufferObject cursor_empty; + + IntPtr gbm_surface; + + public LinuxNativeWindow(IntPtr display, IntPtr gbm, int fd, + int x, int y, int width, int height, string title, + GraphicsMode mode, GameWindowFlags options, + DisplayDevice display_device) + { + Debug.Print("[KMS] Creating window on display {0:x}", display); + + Title = title; + + display_device = display_device ?? DisplayDevice.Default; + if (display_device == null) + { + throw new NotSupportedException("[KMS] Driver does not currently support headless systems"); + } + + window = new LinuxWindowInfo(display, fd, gbm, display_device.Id as LinuxDisplay); + + // Note: we only support fullscreen windows on KMS. + // We implicitly override the requested width and height + // by the width and height of the DisplayDevice, if any. + width = display_device.Width; + height = display_device.Height; + bounds = new Rectangle(0, 0, width, height); + client_size = bounds.Size; + + if (!mode.Index.HasValue) + { + mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0); + } + Debug.Print("[KMS] Selected EGL mode {0}", mode); + + SurfaceFormat format = GetSurfaceFormat(display, mode); + SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; + if (!Gbm.IsFormatSupported(gbm, format, usage)) + { + Debug.Print("[KMS] Failed to find suitable surface format, using XRGB8888"); + format = SurfaceFormat.XRGB8888; + } + + Debug.Print("[KMS] Creating GBM surface on {0:x} with {1}x{2} {3} [{4}]", + gbm, width, height, format, usage); + IntPtr gbm_surface = Gbm.CreateSurface(gbm, + width, height, format, usage); + if (gbm_surface == IntPtr.Zero) + { + throw new NotSupportedException("[KMS] Failed to create GBM surface for rendering"); + } + + window.Handle = gbm_surface; + Debug.Print("[KMS] Created GBM surface {0:x}", window.Handle); + + window.CreateWindowSurface(mode.Index.Value); + Debug.Print("[KMS] Created EGL surface {0:x}", window.Surface); + + cursor_default = CreateCursor(gbm, Cursors.Default); + cursor_empty = CreateCursor(gbm, Cursors.Empty); + Cursor = MouseCursor.Default; + exists = true; + } + + #region Private Members + + static BufferObject CreateCursor(IntPtr gbm, MouseCursor cursor) + { + if (cursor.Width > 64 || cursor.Height > 64) + { + Debug.Print("[KMS] Cursor size {0}x{1} unsupported. Maximum is 64x64.", + cursor.Width, cursor.Height); + return default(BufferObject); + } + + int width = 64; + int height = 64; + SurfaceFormat format = SurfaceFormat.ARGB8888; + SurfaceFlags usage = SurfaceFlags.Cursor64x64 | SurfaceFlags.Write; + + Debug.Print("[KMS] Gbm.CreateBuffer({0:X}, {1}, {2}, {3}, {4}).", + gbm, width, height, format, usage); + + BufferObject bo = Gbm.CreateBuffer( + gbm, width, height, format, usage); + + if (bo == BufferObject.Zero) + { + Debug.Print("[KMS] Failed to create buffer."); + return bo; + } + + // Copy cursor.Data into a new buffer of the correct size + byte[] cursor_data = new byte[width * height * 4]; + for (int y = 0; y < cursor.Height; y++) + { + int dst_offset = y * width * 4; + int src_offset = y * cursor.Width * 4; + int src_length = cursor.Width * 4; + Array.Copy( + cursor.Data, src_offset, + cursor_data, dst_offset, + src_length); + } + bo.Write(cursor_data); + + return bo; + } + + void SetCursor(MouseCursor cursor) + { + BufferObject bo = default(BufferObject); + if (cursor == MouseCursor.Default) + { + bo = cursor_default; + } + else if (cursor == MouseCursor.Empty) + { + bo = cursor_empty; + } + else + { + if (cursor_custom != BufferObject.Zero) + cursor_custom.Dispose(); + cursor_custom = CreateCursor(window.BufferManager, cursor); + bo = cursor_custom; + } + + // If we failed to create a proper cursor, try falling back + // to the empty cursor. We do not want to crash here! + if (bo == BufferObject.Zero) + { + bo = cursor_empty; + } + + if (bo != BufferObject.Zero) + { + Drm.SetCursor(window.FD, window.DisplayDevice.Id, + bo.Handle, bo.Width, bo.Height, cursor.X, cursor.Y); + } + } + + static SurfaceFormat GetSurfaceFormat(IntPtr display, GraphicsMode mode) + { + // Use EGL 1.4 EGL_NATIVE_VISUAL_ID to retrieve + // the corresponding surface format. If that fails + // fall back to a manual algorithm. + int format; + Egl.GetConfigAttrib(display, mode.Index.Value, + Egl.NATIVE_VISUAL_ID, out format); + if ((SurfaceFormat)format != 0) + return (SurfaceFormat)format; + + Debug.Print("[KMS] Failed to retrieve EGL visual from GBM surface. Error: {0}", + Egl.GetError()); + Debug.Print("[KMS] Falling back to hardcoded formats."); + + int r = mode.ColorFormat.Red; + int g = mode.ColorFormat.Green; + int b = mode.ColorFormat.Blue; + int a = mode.ColorFormat.Alpha; + + if (mode.ColorFormat.IsIndexed) + return SurfaceFormat.C8; + if (r == 3 && g == 3 && b == 2 && a == 0) + return SurfaceFormat.RGB332; + if (r == 5 && g == 6 && b == 5 && a == 0) + return SurfaceFormat.RGB565; + if (r == 5 && g == 6 && b == 5 && a == 0) + return SurfaceFormat.RGB565; + if (r == 8 && g == 8 && b == 8 && a == 0) + return SurfaceFormat.RGB888; + if (r == 5 && g == 5 && b == 5 && a == 1) + return SurfaceFormat.RGBA5551; + if (r == 10 && g == 10 && b == 10 && a == 2) + return SurfaceFormat.RGBA1010102; + if (r == 4 && g == 4 && b == 4 && a == 4) + return SurfaceFormat.RGBA4444; + if (r == 8 && g == 8 && b == 8 && a == 8) + return SurfaceFormat.RGBA8888; + + return SurfaceFormat.RGBA8888; + } + + KeyboardState ProcessKeyboard(KeyboardState keyboard) + { + for (Key i = 0; i < Key.LastKey; i++) + { + if (keyboard[i]) + { + OnKeyDown(i, previous_keyboard[i]); + // Todo: implement libxkb-common binding for text input + } + + if (!keyboard[i] && previous_keyboard[i]) + { + OnKeyUp(i); + } + } + return keyboard; + } + + MouseState ProcessMouse(MouseState mouse) + { + // Handle mouse buttons + for (MouseButton i = 0; i < MouseButton.LastButton; i++) + { + if (mouse[i] && !previous_mouse[i]) + { + OnMouseDown(i); + } + + if (!mouse[i] && previous_mouse[i]) + { + OnMouseUp(i); + } + } + + // Handle mouse movement + { + int x = mouse.X; + int y = mouse.Y; + + // Make sure the mouse cannot leave the GameWindow when captured + if (!CursorVisible) + { + x = MathHelper.Clamp(mouse.X, Bounds.Left, Bounds.Right - 1); + y = MathHelper.Clamp(mouse.Y, Bounds.Top, Bounds.Bottom - 1); + if (x != mouse.X || y != mouse.Y) + { + Mouse.SetPosition(x, y); + } + } + + if (x != previous_mouse.X || y != previous_mouse.Y) + { + OnMouseMove(x, y); + } + } + + // Handle mouse scroll + if (mouse.Scroll != previous_mouse.Scroll) + { + float dx = mouse.Scroll.X - previous_mouse.Scroll.X; + float dy = mouse.Scroll.Y - previous_mouse.Scroll.Y; + OnMouseWheel(dx, dy); + } + + // Handle mouse focus + // Note: focus follows mouse. Literally. + bool cursor_in = Bounds.Contains(new Point(mouse.X, mouse.Y)); + if (!cursor_in && Focused) + { + OnMouseLeave(EventArgs.Empty); + SetFocus(false); + } + else if (cursor_in && !Focused) + { + OnMouseEnter(EventArgs.Empty); + SetFocus(true); + } + + return mouse; + } + + void SetFocus(bool focus) + { + if (is_focused != focus) + { + is_focused = focus; + OnFocusedChanged(EventArgs.Empty); + } + } + + #endregion + + #region INativeWindow Members + + public override void ProcessEvents() + { + // Note: there is no event-based keyboard/mouse input available. + // We will fake that by polling OpenTK.Input. + previous_keyboard = ProcessKeyboard(Keyboard.GetState()); + previous_mouse = ProcessMouse(Mouse.GetCursorState()); + + base.ProcessEvents(); + } + + public override void Close() + { + exists = false; + } + + public override Point PointToClient(Point point) + { + var origin = Point.Empty; + var display = DisplayDevice.Default; + if (display != null) + { + origin = display.Bounds.Location; + } + var client = Location; + return new Point(point.X + client.X - origin.X, point.Y + client.Y - origin.Y); + } + + public override Point PointToScreen(Point point) + { + var origin = Point.Empty; + var display = DisplayDevice.Default; + if (display != null) + { + origin = display.Bounds.Location; + } + var client = Location; + return new Point(point.X + origin.X - client.X, point.Y + origin.Y - client.Y); + + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Debug.Print("[KMS] Destroying window {0}.", window.Handle); + window.Dispose(); + Gbm.DestroySurface(window.Handle); + } + else + { + Debug.Print("[KMS] {0} leaked. Did you forget to call Dispose()?", GetType().FullName); + } + } + + public override Icon Icon + { + get + { + return icon; + } + set + { + if (icon != value) + { + icon = value; + OnIconChanged(EventArgs.Empty); + } + } + } + + public override string Title + { + get + { + return title; + } + set + { + if (title != value) + { + title = value; + OnTitleChanged(EventArgs.Empty); + } + } + } + + public override bool Focused + { + get + { + return is_focused; + } + } + + public override bool Visible + { + get + { + return true; + } + set + { + } + } + + public override bool Exists + { + get + { + return exists; + } + } + + public override IWindowInfo WindowInfo + { + get + { + return window; + } + } + + public override WindowState WindowState + { + get + { + return WindowState.Fullscreen; + } + set + { + } + } + + public override WindowBorder WindowBorder + { + get + { + return WindowBorder.Hidden; + } + set + { + } + } + + public override Rectangle Bounds + { + get + { + return bounds; + } + set + { + } + } + + public override Size ClientSize + { + get + { + return client_size; + } + set + { + } + } + + public override bool CursorVisible + { + get + { + return is_cursor_visible; + } + set + { + if (value && !is_cursor_visible) + { + SetCursor(cursor_current); + } + else if (!value && is_cursor_visible) + { + SetCursor(MouseCursor.Empty); + } + is_cursor_visible = value; + } + } + + public override MouseCursor Cursor + { + get + { + return cursor_current; + } + set + { + if (cursor_current != value) + { + if (cursor_custom != BufferObject.Zero) + { + cursor_custom.Dispose(); + } + + if (CursorVisible) + { + SetCursor(value); + } + cursor_current = value; + } + } + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs new file mode 100644 index 00000000..e1d91e9a --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -0,0 +1,56 @@ +#region License +// +// LinuxWindowInfo.cs +// +// Author: +// Stefanos A. +// +// Copyright (c) 2006-2014 Stefanos Apostolopoulos +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +#endregion + +using System; +using System.Diagnostics; +using OpenTK.Platform.Egl; + +namespace OpenTK.Platform.Linux +{ + class LinuxWindowInfo : EglWindowInfo + { + public int FD { get; private set; } + public LinuxDisplay DisplayDevice { get; private set; } + public IntPtr BufferManager { get; private set; } + + public LinuxWindowInfo(IntPtr display, int fd, IntPtr gbm, LinuxDisplay display_device) + : base(IntPtr.Zero, display, IntPtr.Zero) + { + if (display_device == null) + throw new ArgumentNullException(); + + FD = fd; + BufferManager = gbm; + DisplayDevice = display_device; + // The window handle and surface handle must + // be filled in manually once they are known. + } + } +} + diff --git a/Source/OpenTK/Platform/X11/X11Input.cs b/Source/OpenTK/Platform/X11/X11Input.cs index 50bdf4a3..246501c7 100644 --- a/Source/OpenTK/Platform/X11/X11Input.cs +++ b/Source/OpenTK/Platform/X11/X11Input.cs @@ -37,7 +37,7 @@ namespace OpenTK.Platform.X11 { readonly X11Mouse mouse = new X11Mouse(); readonly X11Keyboard keyboard = new X11Keyboard(); - readonly X11Joystick joystick = new X11Joystick(); + readonly Linux.LinuxJoystick joystick = new Linux.LinuxJoystick(); readonly IGamePadDriver gamepad = new MappedGamePadDriver(); internal X11Input() diff --git a/Source/OpenTK/Platform/X11/XI2Input.cs b/Source/OpenTK/Platform/X11/XI2Input.cs index c2e8528e..d91d2c07 100644 --- a/Source/OpenTK/Platform/X11/XI2Input.cs +++ b/Source/OpenTK/Platform/X11/XI2Input.cs @@ -36,7 +36,7 @@ namespace OpenTK.Platform.X11 class XI2Input : IInputDriver2 { readonly XI2MouseKeyboard mouse_keyboard = new XI2MouseKeyboard(); - readonly X11Joystick joystick = new X11Joystick(); + readonly Linux.LinuxJoystick joystick = new Linux.LinuxJoystick(); readonly IGamePadDriver gamepad = new MappedGamePadDriver(); internal XI2Input()