From c3451530a6ae84ae4625f615acc67430c4a195bb Mon Sep 17 00:00:00 2001 From: thefiddler Date: Mon, 23 Jun 2014 22:22:04 +0200 Subject: [PATCH 01/41] [KMS] Initial implementation --- Source/OpenTK/OpenTK.csproj | 9 + Source/OpenTK/Platform/Linux/Bindings/Drm.cs | 57 +++++ Source/OpenTK/Platform/Linux/Bindings/Gbm.cs | 52 +++++ .../OpenTK/Platform/Linux/Bindings/Linux.cs | 68 ++++++ Source/OpenTK/Platform/Linux/LinuxFactory.cs | 117 +++++++++++ .../Platform/Linux/LinuxNativeWindow.cs | 196 ++++++++++++++++++ 6 files changed, 499 insertions(+) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Drm.cs create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Gbm.cs create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Linux.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxFactory.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 718d8540..d931c244 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -806,6 +806,11 @@ + + + + + @@ -836,4 +841,8 @@ + + + + \ No newline at end of file diff --git a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs new file mode 100644 index 00000000..3f4bddb7 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -0,0 +1,57 @@ +#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 +{ + class Drm + { + const string lib = "libdrm"; + + [DllImport(lib, EntryPoint = "drmModeGetResources", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModeGetResources(int fd); + } + + struct ModeRes + { + public int count_fbs; + public IntPtr fbs; //uint* + public int count_crtcs; + public IntPtr crtcs; //uint* + public int count_connectors; + public IntPtr connectors; //uint* + public int count_encoders; + public IntPtr encoders; //uint* + public int min_width, max_width; + public int min_height, max_height; + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs new file mode 100644 index 00000000..6ca1e6bf --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -0,0 +1,52 @@ +#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 +{ + class Gbm + { + const string lib = "gbm"; + + [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateDevice(int fd); + } + + struct GbmDevice + { + public int id; + public int fd; + public IntPtr name; + public int refcount; + public Stat stat; + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Linux.cs b/Source/OpenTK/Platform/Linux/Bindings/Linux.cs new file mode 100644 index 00000000..2640115d --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Linux.cs @@ -0,0 +1,68 @@ +#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; + +namespace OpenTK.Platform.Linux +{ + class Linux + { + [DllImport("glib", EntryPoint = "open", CallingConvention = CallingConvention.Cdecl)] + public static extern int Open(string pathname, OpenFlags flags); + } + + [Flags] + enum OpenFlags + { + ReadOnly = 0, + WriteOnly = 1, + ReadWrite = 2, + CloseOnExec = 4096 + } + + [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 */ + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs new file mode 100644 index 00000000..f998a071 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -0,0 +1,117 @@ +#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.Runtime.InteropServices; +using OpenTK.Graphics; +using OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + // Linux KMS platform + class LinuxFactory : PlatformFactoryBase + { + int fd; + GbmDevice device; + IntPtr display; + + public LinuxFactory() + { + // Todo: support multi-GPU systems + string gpu = "/dev/dri/card0"; + fd = Linux.Open(gpu, OpenFlags.ReadOnly | OpenFlags.CloseOnExec); + if (fd < 0) + { + throw new NotSupportedException("No KMS-capable GPU available"); + } + Debug.Print("GPU {0} opened as fd:{1}", gpu, fd); + + IntPtr dev = Gbm.CreateDevice(fd); + if (dev == IntPtr.Zero) + { + throw new NotSupportedException("Failed to create GBM device"); + } + device = (GbmDevice)Marshal.PtrToStructure(dev, typeof(GbmDevice)); + Debug.Print("GBM {0:x} '{1}' created successfully", + dev, Marshal.PtrToStringAnsi(device.name)); + + display = Egl.Egl.GetDisplay(dev); + if (display == IntPtr.Zero) + { + throw new NotSupportedException("Failed to create EGL display"); + } + Debug.Print("EGL display {0:x} created successfully", display); + + int major, minor; + if (!Egl.Egl.Initialize(display, out major, out minor)) + { + int error = Egl.Egl.GetError(); + throw new NotSupportedException("Failed to initialize EGL display. Error code: " + error); + } + Debug.Print("EGL {0}.{1} initialized successfully on display {2:x}", major, minor, display); + } + + public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice device) + { + return new LinuxNativeWindow(x, y, width, height, title, mode, options, device); + } + + public override IDisplayDeviceDriver CreateDisplayDeviceDriver() + { + throw new NotImplementedException(); + } + + public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) + { + throw new NotImplementedException(); + } + + public override GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext() + { + throw new NotImplementedException(); + } + + public override IKeyboardDriver2 CreateKeyboardDriver() + { + throw new NotImplementedException(); + } + + public override IMouseDriver2 CreateMouseDriver() + { + throw new NotImplementedException(); + } + + public override IJoystickDriver2 CreateJoystickDriver() + { + throw new NotImplementedException(); + } + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs new file mode 100644 index 00000000..a44cb9bb --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -0,0 +1,196 @@ +#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; + +namespace OpenTK.Platform.Linux +{ + class LinuxNativeWindow : NativeWindowBase + { + #region INativeWindow Members + + public override void Close() + { + throw new NotImplementedException(); + } + + public override System.Drawing.Point PointToClient(System.Drawing.Point point) + { + throw new NotImplementedException(); + } + + public override System.Drawing.Point PointToScreen(System.Drawing.Point point) + { + throw new NotImplementedException(); + } + + protected override void Dispose(bool disposing) + { + throw new NotImplementedException(); + } + + public override System.Drawing.Icon Icon + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override string Title + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override bool Focused + { + get + { + throw new NotImplementedException(); + } + } + + public override bool Visible + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override bool Exists + { + get + { + throw new NotImplementedException(); + } + } + + public override IWindowInfo WindowInfo + { + get + { + throw new NotImplementedException(); + } + } + + public override WindowState WindowState + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override WindowBorder WindowBorder + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override System.Drawing.Rectangle Bounds + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override System.Drawing.Size ClientSize + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override bool CursorVisible + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + public override MouseCursor Cursor + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + #endregion + + + + } +} + From 6f6798de626068a9aaafe0f3223cb45607a4c2df Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 24 Jun 2014 19:27:38 +0200 Subject: [PATCH 02/41] [KMS] Added new Linux KMS driver --- Source/Examples/Main.cs | 9 + Source/OpenTK/OpenTK.csproj | 10 +- Source/OpenTK/Platform/Factory.cs | 4 +- Source/OpenTK/Platform/Linux/Bindings/Drm.cs | 107 ++++++- .../Linux/Bindings/{Linux.cs => Libc.cs} | 70 ++++- .../Platform/Linux/LinuxDisplayDriver.cs | 282 ++++++++++++++++++ Source/OpenTK/Platform/Linux/LinuxFactory.cs | 71 +++-- .../X11Joystick.cs => Linux/LinuxJoystick.cs} | 119 ++------ .../Platform/Linux/LinuxNativeWindow.cs | 92 ++++-- .../OpenTK/Platform/Linux/LinuxWindowInfo.cs | 43 +++ Source/OpenTK/Platform/X11/X11Input.cs | 2 +- Source/OpenTK/Platform/X11/XI2Input.cs | 2 +- 12 files changed, 643 insertions(+), 168 deletions(-) rename Source/OpenTK/Platform/Linux/Bindings/{Linux.cs => Libc.cs} (53%) create mode 100644 Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs rename Source/OpenTK/Platform/{X11/X11Joystick.cs => Linux/LinuxJoystick.cs} (75%) create mode 100644 Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs diff --git a/Source/Examples/Main.cs b/Source/Examples/Main.cs index 84dba6d7..eb86bd06 100644 --- a/Source/Examples/Main.cs +++ b/Source/Examples/Main.cs @@ -101,6 +101,15 @@ namespace Examples public static void Main(string[] args) { Trace.Listeners.Add(new ConsoleTraceListener()); + Tests.GameWindowStates.Main(); + return; + + using (var gw = new GameWindow()) + { + gw.KeyDown += (sender, e) => gw.Exit(); + gw.Run(60); + } + return; if (args.Length > 0) { diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index d931c244..794abfa5 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -343,9 +343,6 @@ Code - - Code - Code @@ -809,8 +806,13 @@ - + + + Code + + + diff --git a/Source/OpenTK/Platform/Factory.cs b/Source/OpenTK/Platform/Factory.cs index 9a701844..5cc0bfe1 100644 --- a/Source/OpenTK/Platform/Factory.cs +++ b/Source/OpenTK/Platform/Factory.cs @@ -53,6 +53,7 @@ namespace OpenTK.Platform // Create regular platform backend if (Configuration.RunningOnSdl2) Default = new SDL2.Sdl2Factory(); + else if (Configuration.RunningOnLinux) Default = new Linux.LinuxFactory(); else if (Configuration.RunningOnX11) Default = new X11.X11Factory(); else if (Configuration.RunningOnWindows) Default = new Windows.WinFactory(); else if (Configuration.RunningOnMacOS) Default = new MacOS.MacOSFactory(); @@ -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 index 3f4bddb7..7f2bf590 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -36,20 +36,115 @@ namespace OpenTK.Platform.Linux { const string lib = "libdrm"; + [DllImport(lib, EntryPoint = "drmModeGetCrtc", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ModeGetCrtc(int fd, uint 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, uint connector_id); + + [DllImport(lib, EntryPoint = "drmModeGetEncoder", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr ModeGetEncoder(int fd, uint encoder_id); + [DllImport(lib, EntryPoint = "drmModeGetResources", CallingConvention = CallingConvention.Cdecl)] - static extern IntPtr ModeGetResources(int fd); + public static extern IntPtr ModeGetResources(int fd); + + [DllImport(lib, EntryPoint = "drmModeSetCrtc", CallingConvention = CallingConvention.Cdecl)] + unsafe public static extern int ModeSetCrtc(int fd, uint crtcId, uint bufferId, + uint x, uint y, uint* connectors, int count, ModeInfo* mode); } - struct ModeRes + enum ModeConnection : byte + { + Connected = 1, + Disconnected = 2, + Unknown = 3 + } + + enum ModeSubPixel : byte + { + Unknown = 1, + HorizontalRgb = 2, + HorizontalBgr = 3, + VerticalRgb = 4, + VerticalBgr = 5, + None = 6 + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct ModeConnector + { + public uint connector_id; + public uint encoder_id; + public uint connector_type; + public uint connector_type_id; + public ModeConnection connection; + public uint mmWidth, mmHeight; + public ModeSubPixel subpixel; + + public int count_modes; + public ModeInfo* modes; + + public int count_props; + public uint *props; + public ulong *prop_values; + + public int count_encoders; + public uint *encoders; + } + + struct ModeCrtc + { + public uint crtc_id; + public uint buffer_id; + + public uint x, y; + public uint width, height; + public int mode_valid; + public ModeInfo mode; + + public int gamma_size; + } + + struct ModeEncoder + { + public uint encoder_id; + public uint encoder_type; + public uint crtc_id; + public uint possible_crtcs; + public uint 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 uint 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 IntPtr fbs; //uint* + public uint* fbs; public int count_crtcs; - public IntPtr crtcs; //uint* + public uint* crtcs; public int count_connectors; - public IntPtr connectors; //uint* + public uint* connectors; public int count_encoders; - public IntPtr encoders; //uint* + public uint* encoders; public int min_width, max_width; public int min_height, max_height; } diff --git a/Source/OpenTK/Platform/Linux/Bindings/Linux.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs similarity index 53% rename from Source/OpenTK/Platform/Linux/Bindings/Linux.cs rename to Source/OpenTK/Platform/Linux/Bindings/Libc.cs index 2640115d..c571e6d8 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Linux.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -29,22 +29,62 @@ using System; using System.Runtime.InteropServices; +using System.Text; namespace OpenTK.Platform.Linux { - class Linux + class Libc { - [DllImport("glib", EntryPoint = "open", CallingConvention = CallingConvention.Cdecl)] - public static extern int Open(string pathname, OpenFlags flags); + const string lib = "libc"; + + [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 open([MarshalAs(UnmanagedType.LPStr)]string 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); } [Flags] enum OpenFlags { - ReadOnly = 0, - WriteOnly = 1, - ReadWrite = 2, - CloseOnExec = 4096 + 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) } [StructLayout(LayoutKind.Sequential)] @@ -64,5 +104,21 @@ namespace OpenTK.Platform.Linux 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/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs new file mode 100644 index 00000000..8feefe42 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -0,0 +1,282 @@ +#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 +{ + class LinuxDisplayDriver : DisplayDeviceBase + { + unsafe class LinuxDisplay + { + public ModeConnector* Connector; + public ModeEncoder* Encoder; + public ModeCrtc* Crtc; + public ModeInfo Mode + { + get + { + if (Crtc == null) + throw new InvalidOperationException(); + + return Crtc->mode; + } + } + public ModeInfo OriginalMode; + + public int Id + { + get + { + if (Crtc == null) + throw new InvalidOperationException(); + + return (int)Crtc->crtc_id; + } + } + + public LinuxDisplay(ModeConnector* c, ModeEncoder* e, ModeCrtc* r) + { + Connector = c; + Encoder = e; + Crtc = r; + OriginalMode = Crtc->mode; // in case we change resolution later on + } + } + + readonly int FD; + readonly GbmDevice Device; + readonly Dictionary DisplayIds = + new Dictionary(); + + public LinuxDisplayDriver(int fd) + { + FD = fd; + QueryDisplays(); + } + + void QueryDisplays() + { + unsafe + { + lock (this) + { + AvailableDevices.Clear(); + DisplayIds.Clear(); + + ModeRes* resources = (ModeRes*)Drm.ModeGetResources(FD); + if (resources == null) + { + throw new NotSupportedException("[KMS] DRM ModeGetResources failed"); + } + 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) + { + if (connector->connection == ModeConnection.Connected && + connector->count_modes > 0) + { + // Connector found! + AddDisplay(connector); + } + else + { + // This is not the display we are looking for + Drm.ModeFreeConnector((IntPtr)connector); + connector = null; + } + } + } + + if (AvailableDevices.Count == 0) + { + Debug.Print("[KMS] Failed to find any active displays"); + } + } + } + } + + unsafe void AddDisplay(ModeConnector* c) + { + // Find corresponding encoder + 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; + } + + 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; + } + + LinuxDisplay display = new LinuxDisplay(c, encoder, crtc); + if (!DisplayIds.ContainsKey(display.Id)) + { + DisplayIds.Add(display.Id, AvailableDevices.Count); + } + + List modes = new List(); + for (int i = 0; i < display.Connector->count_modes; i++) + { + ModeInfo* mode = display.Connector->modes + i; + DisplayResolution res = GetDisplayResolution(mode); + modes.Add(res); + } + ModeInfo current_mode = display.Mode; + DisplayResolution current = GetDisplayResolution(¤t_mode); + + // 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. + System.Drawing.Rectangle bounds = + new System.Drawing.Rectangle( + AvailableDevices.Count == 0 ? 0 : AvailableDevices[0].Bounds.Right, + 0, + current.Width, + current.Height); + + DisplayDevice device = new DisplayDevice( + current, + AvailableDevices.Count == 0, + modes, + bounds, + display); + + if (AvailableDevices.Count == 0) + { + Primary = device; + } + + AvailableDevices.Add(device); + } + + unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode) + { + if (mode == null) + throw new ArgumentNullException(); + + return new DisplayResolution( + 0, 0, + mode->htotal, mode->vtotal, + 32, // This is actually part of the framebuffer, not the DisplayResolution + mode->vrefresh / 1000.0f); + } + + unsafe static ModeInfo* GetModeInfo(LinuxDisplay display, DisplayResolution resolution) + { + for (int i = 0; i < display.Connector->count_modes; i++) + { + ModeInfo* mode = display.Connector->modes + i; + if (mode != null && + mode->htotal == resolution.Width && + mode->vtotal == 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); + uint connector_id = display.Connector->connector_id; + if (mode != null) + { + return Drm.ModeSetCrtc(FD, (uint)display.Id, 0, 0, 0, + &connector_id, 1, mode) == 0; + } + return false; + } + } + + public override bool TryRestoreResolution(DisplayDevice device) + { + unsafe + { + LinuxDisplay display = (LinuxDisplay)device.Id; + uint connector_id = display.Connector->connector_id; + ModeInfo mode = display.OriginalMode; + return Drm.ModeSetCrtc(FD, (uint)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 index f998a071..f8b1096d 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -32,70 +32,93 @@ using System.Diagnostics; 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; - GbmDevice device; + GbmDevice gbm_device; IntPtr display; + IJoystickDriver2 JoystickDriver; + IDisplayDeviceDriver DisplayDriver; + public LinuxFactory() + { + SetupEgl(); + } + + #region Private Members + + void SetupEgl() { // Todo: support multi-GPU systems string gpu = "/dev/dri/card0"; - fd = Linux.Open(gpu, OpenFlags.ReadOnly | OpenFlags.CloseOnExec); + fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); if (fd < 0) { - throw new NotSupportedException("No KMS-capable GPU available"); + throw new NotSupportedException("[KMS] No KMS-capable GPU available"); } - Debug.Print("GPU {0} opened as fd:{1}", gpu, fd); + Debug.Print("[KMS] GPU '{0}' opened as fd:{1}", gpu, fd); IntPtr dev = Gbm.CreateDevice(fd); if (dev == IntPtr.Zero) { - throw new NotSupportedException("Failed to create GBM device"); + throw new NotSupportedException("[KMS] Failed to create GBM device"); } - device = (GbmDevice)Marshal.PtrToStructure(dev, typeof(GbmDevice)); - Debug.Print("GBM {0:x} '{1}' created successfully", - dev, Marshal.PtrToStringAnsi(device.name)); + gbm_device = (GbmDevice)Marshal.PtrToStructure(dev, typeof(GbmDevice)); + Debug.Print("[KMS] GBM {0:x} created successfully", dev); - display = Egl.Egl.GetDisplay(dev); + display = Egl.GetDisplay(dev); if (display == IntPtr.Zero) { - throw new NotSupportedException("Failed to create EGL display"); + throw new NotSupportedException("[KMS] Failed to create EGL display"); } - Debug.Print("EGL display {0:x} created successfully", display); + Debug.Print("[KMS] EGL display {0:x} created successfully", display); int major, minor; - if (!Egl.Egl.Initialize(display, out major, out minor)) + if (!Egl.Initialize(display, out major, out minor)) { - int error = Egl.Egl.GetError(); - throw new NotSupportedException("Failed to initialize EGL display. Error code: " + error); + int error = Egl.GetError(); + throw new NotSupportedException("[KMS] Failed to initialize EGL display. Error code: " + error); } - Debug.Print("EGL {0}.{1} initialized successfully on display {2:x}", major, minor, display); + Debug.Print("[KMS] EGL {0}.{1} initialized successfully on display {2:x}", major, minor, display); } - public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice device) + #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(x, y, width, height, title, mode, options, device); + return new LinuxNativeWindow(display, gbm_device, width, height, title, mode, options, display_device); } public override IDisplayDeviceDriver CreateDisplayDeviceDriver() { - throw new NotImplementedException(); + lock (this) + { + DisplayDriver = DisplayDriver ?? new LinuxDisplayDriver(fd); + return DisplayDriver; + } } public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) { - throw new NotImplementedException(); + return new EglUnixContext(mode, (EglWindowInfo)window, shareContext, major, minor, flags); } public override GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext() { - throw new NotImplementedException(); + return (GraphicsContext.GetCurrentContextDelegate)delegate + { + return new ContextHandle(Egl.GetCurrentContext()); + }; } public override IKeyboardDriver2 CreateKeyboardDriver() @@ -110,8 +133,14 @@ namespace OpenTK.Platform.Linux public override IJoystickDriver2 CreateJoystickDriver() { - throw new NotImplementedException(); + lock (this) + { + JoystickDriver = JoystickDriver ?? new LinuxJoystick(); + return JoystickDriver; + } } + + #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/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index a44cb9bb..6d926923 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -28,42 +28,74 @@ #endregion using System; +using System.Drawing; +using OpenTK.Graphics; +using OpenTK.Platform.Egl; namespace OpenTK.Platform.Linux { + using Egl = OpenTK.Platform.Egl.Egl; + class LinuxNativeWindow : NativeWindowBase { + LinuxWindowInfo window_info; + string title; + Icon icon; + bool exists; + Rectangle bounds; + Size client_size; + + public LinuxNativeWindow(IntPtr display, GbmDevice device, + int width, int height, string title, GraphicsMode mode, GameWindowFlags options, + DisplayDevice display_device) + { + Title = title; + bounds = new Rectangle(0, 0, width, height); + client_size = bounds.Size; + + //window_info = new LinuxWindowInfo( + // Egl.CreateWindowSurface( + + exists = true; + } + #region INativeWindow Members public override void Close() { - throw new NotImplementedException(); + exists = false; } - public override System.Drawing.Point PointToClient(System.Drawing.Point point) + public override Point PointToClient(Point point) { - throw new NotImplementedException(); + // Todo + return point; } - public override System.Drawing.Point PointToScreen(System.Drawing.Point point) + public override Point PointToScreen(Point point) { - throw new NotImplementedException(); + // Todo + return point; } protected override void Dispose(bool disposing) { - throw new NotImplementedException(); + // Todo } - public override System.Drawing.Icon Icon + public override Icon Icon { get { - throw new NotImplementedException(); + return icon; } set { - throw new NotImplementedException(); + if (icon != value) + { + icon = value; + OnIconChanged(EventArgs.Empty); + } } } @@ -71,11 +103,15 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return title; } set { - throw new NotImplementedException(); + if (title != value) + { + title = value; + OnTitleChanged(EventArgs.Empty); + } } } @@ -83,7 +119,7 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return true; } } @@ -91,11 +127,10 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return true; } set { - throw new NotImplementedException(); } } @@ -103,7 +138,7 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return exists; } } @@ -111,7 +146,7 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return window_info; } } @@ -119,11 +154,10 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return WindowState.Fullscreen; } set { - throw new NotImplementedException(); } } @@ -131,35 +165,32 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return WindowBorder.Hidden; } set { - throw new NotImplementedException(); } } - public override System.Drawing.Rectangle Bounds + public override Rectangle Bounds { get { - throw new NotImplementedException(); + return bounds; } set { - throw new NotImplementedException(); } } - public override System.Drawing.Size ClientSize + public override Size ClientSize { get { - throw new NotImplementedException(); + return client_size; } set { - throw new NotImplementedException(); } } @@ -167,11 +198,10 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return false; } set { - throw new NotImplementedException(); } } @@ -179,18 +209,14 @@ namespace OpenTK.Platform.Linux { get { - throw new NotImplementedException(); + return MouseCursor.Empty; } set { - throw new NotImplementedException(); } } #endregion - - - } } diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs new file mode 100644 index 00000000..df564285 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -0,0 +1,43 @@ +#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 OpenTK.Platform.Egl; + +namespace OpenTK.Platform.Linux +{ + class LinuxWindowInfo : EglWindowInfo + { + public LinuxWindowInfo(IntPtr handle, IntPtr display, IntPtr surface) + : base(handle, display, surface) + { + } + } +} + 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() From 6454822116cd3b1a1b7992be7a383b7be6626cc9 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 25 Jun 2014 09:01:35 +0200 Subject: [PATCH 03/41] [KMS] Create window surface --- Source/OpenTK/Platform/Egl/EglGraphicsMode.cs | 9 ++ Source/OpenTK/Platform/Egl/EglWindowInfo.cs | 4 +- Source/OpenTK/Platform/Linux/Bindings/Gbm.cs | 104 ++++++++++++++++-- .../Platform/Linux/LinuxDisplayDriver.cs | 12 +- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 13 +-- .../Platform/Linux/LinuxNativeWindow.cs | 82 ++++++++++++-- .../OpenTK/Platform/Linux/LinuxWindowInfo.cs | 6 +- 7 files changed, 197 insertions(+), 33 deletions(-) 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/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/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs index 6ca1e6bf..9225e442 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -32,21 +32,111 @@ using System.Runtime.InteropServices; namespace OpenTK.Platform.Linux { + using GbmDevice = IntPtr; // opaque pointer "struct gbm_device*" + class Gbm { const string lib = "gbm"; [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr CreateDevice(int fd); + public static extern GbmDevice CreateDevice(int fd); + + [DllImport(lib, EntryPoint = "gbm_surface_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateSurface(GbmDevice 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(GbmDevice gbm, SurfaceFormat format, SurfaceFlags usage); } - struct GbmDevice + enum SurfaceFormat { - public int id; - public int fd; - public IntPtr name; - public int refcount; - public Stat stat; + 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), } } diff --git a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs index 8feefe42..a12fd06e 100644 --- a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -75,7 +75,6 @@ namespace OpenTK.Platform.Linux } readonly int FD; - readonly GbmDevice Device; readonly Dictionary DisplayIds = new Dictionary(); @@ -203,19 +202,16 @@ namespace OpenTK.Platform.Linux 0, current.Width, current.Height); - - DisplayDevice device = new DisplayDevice( - current, - AvailableDevices.Count == 0, - modes, - bounds, - display); + bool is_primary = AvailableDevices.Count == 0; + DisplayDevice device = new DisplayDevice(current, is_primary, + modes, bounds, display); if (AvailableDevices.Count == 0) { Primary = device; } + Debug.Print("[KMS] Detected display {0}", device); AvailableDevices.Add(device); } diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index f8b1096d..2c962ed1 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -42,7 +42,7 @@ namespace OpenTK.Platform.Linux class LinuxFactory : PlatformFactoryBase { int fd; - GbmDevice gbm_device; + IntPtr gbm_device; IntPtr display; IJoystickDriver2 JoystickDriver; @@ -66,15 +66,14 @@ namespace OpenTK.Platform.Linux } Debug.Print("[KMS] GPU '{0}' opened as fd:{1}", gpu, fd); - IntPtr dev = Gbm.CreateDevice(fd); - if (dev == IntPtr.Zero) + gbm_device = Gbm.CreateDevice(fd); + if (gbm_device == IntPtr.Zero) { throw new NotSupportedException("[KMS] Failed to create GBM device"); } - gbm_device = (GbmDevice)Marshal.PtrToStructure(dev, typeof(GbmDevice)); - Debug.Print("[KMS] GBM {0:x} created successfully", dev); + Debug.Print("[KMS] GBM {0:x} created successfully; ", gbm_device); - display = Egl.GetDisplay(dev); + display = Egl.GetDisplay(gbm_device); if (display == IntPtr.Zero) { throw new NotSupportedException("[KMS] Failed to create EGL display"); @@ -96,7 +95,7 @@ namespace OpenTK.Platform.Linux public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) { - return new LinuxNativeWindow(display, gbm_device, width, height, title, mode, options, display_device); + return new LinuxNativeWindow(display, gbm_device, x, y, width, height, title, mode, options, display_device); } public override IDisplayDeviceDriver CreateDisplayDeviceDriver() diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index 6d926923..5c0da51b 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -28,7 +28,9 @@ #endregion using System; +using System.Diagnostics; using System.Drawing; +using System.Runtime.InteropServices; using OpenTK.Graphics; using OpenTK.Platform.Egl; @@ -38,27 +40,89 @@ namespace OpenTK.Platform.Linux class LinuxNativeWindow : NativeWindowBase { - LinuxWindowInfo window_info; + LinuxWindowInfo window; string title; Icon icon; bool exists; Rectangle bounds; Size client_size; - public LinuxNativeWindow(IntPtr display, GbmDevice device, - int width, int height, string title, GraphicsMode mode, GameWindowFlags options, + public LinuxNativeWindow(IntPtr display, IntPtr gbm, + 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; bounds = new Rectangle(0, 0, width, height); client_size = bounds.Size; - //window_info = new LinuxWindowInfo( - // Egl.CreateWindowSurface( + window = new LinuxWindowInfo(display); + if (!mode.Index.HasValue) + { + mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0); + } + Debug.Print("[KMS] Selected EGL mode {0}", mode); + unsafe + { + SurfaceFormat format = GetSurfaceFormat(mode); + SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; + if (!Gbm.IsFormatSupported(gbm, format, usage)) + { + format = SurfaceFormat.XBGR8888; + } + + 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); + + // Todo: create mouse cursor exists = true; } + SurfaceFormat GetSurfaceFormat(GraphicsMode mode) + { + 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; + } + #region INativeWindow Members public override void Close() @@ -80,7 +144,11 @@ namespace OpenTK.Platform.Linux protected override void Dispose(bool disposing) { - // Todo + if (disposing) + { + window.Dispose(); + Gbm.DestroySurface(window.Handle); + } } public override Icon Icon @@ -146,7 +214,7 @@ namespace OpenTK.Platform.Linux { get { - return window_info; + return window; } } diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs index df564285..0a1f0933 100644 --- a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -34,9 +34,11 @@ namespace OpenTK.Platform.Linux { class LinuxWindowInfo : EglWindowInfo { - public LinuxWindowInfo(IntPtr handle, IntPtr display, IntPtr surface) - : base(handle, display, surface) + public LinuxWindowInfo(IntPtr display) + : base(IntPtr.Zero, display, IntPtr.Zero) { + // The window handle and surface handle must + // be filled in manually once they are known. } } } From f9d20b5e3e03dfcde9968a544995aa5e66a99633 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 25 Jun 2014 09:11:47 +0200 Subject: [PATCH 04/41] [KMS] Improved GBM surface format selection --- .../Platform/Linux/LinuxNativeWindow.cs | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index 5c0da51b..ee3bf89a 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -65,28 +65,25 @@ namespace OpenTK.Platform.Linux } Debug.Print("[KMS] Selected EGL mode {0}", mode); - unsafe + SurfaceFormat format = GetSurfaceFormat(display, mode); + SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; + if (!Gbm.IsFormatSupported(gbm, format, usage)) { - SurfaceFormat format = GetSurfaceFormat(mode); - SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; - if (!Gbm.IsFormatSupported(gbm, format, usage)) - { - format = SurfaceFormat.XBGR8888; - } - - 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); + //format = SurfaceFormat.xrgba; } + 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); @@ -94,8 +91,21 @@ namespace OpenTK.Platform.Linux exists = true; } - SurfaceFormat GetSurfaceFormat(GraphicsMode mode) + 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; From 5da5ac0fa7f4a09a1bf08a549b4545fe6642d8ab Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 25 Jun 2014 09:17:20 +0200 Subject: [PATCH 05/41] [EGL] Egl.GetError() now returns ErrorCode --- Source/OpenTK/Platform/Egl/Egl.cs | 34 +++++++++++--------- Source/OpenTK/Platform/Egl/EglContext.cs | 3 +- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/Source/OpenTK/Platform/Egl/Egl.cs b/Source/OpenTK/Platform/Egl/Egl.cs index f271f5db..c4fa3837 100644 --- a/Source/OpenTK/Platform/Egl/Egl.cs +++ b/Source/OpenTK/Platform/Egl/Egl.cs @@ -50,6 +50,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 +78,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 +182,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); diff --git a/Source/OpenTK/Platform/Egl/EglContext.cs b/Source/OpenTK/Platform/Egl/EglContext.cs index 37398f00..a6f961ec 100644 --- a/Source/OpenTK/Platform/Egl/EglContext.cs +++ b/Source/OpenTK/Platform/Egl/EglContext.cs @@ -133,7 +133,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/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 2c962ed1..30661205 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -83,7 +83,7 @@ namespace OpenTK.Platform.Linux int major, minor; if (!Egl.Initialize(display, out major, out minor)) { - int error = Egl.GetError(); + 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, display); From d8adf92febd4090c095c647b01bffe80780c5740 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 25 Jun 2014 09:35:20 +0200 Subject: [PATCH 06/41] [EGL] Added support for desktop GL rendering On Linux, EGL can be used to initialize a desktop GL context. This is especially true on KMS, Wayland and Mir. --- Source/OpenTK/Platform/Egl/Egl.cs | 9 +++++- Source/OpenTK/Platform/Egl/EglContext.cs | 14 +++++++- Source/OpenTK/Platform/Egl/EglUnixContext.cs | 34 ++++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/Source/OpenTK/Platform/Egl/Egl.cs b/Source/OpenTK/Platform/Egl/Egl.cs index c4fa3837..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 { @@ -229,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 a6f961ec..ee9d4939 100644 --- a/Source/OpenTK/Platform/Egl/EglContext.cs +++ b/Source/OpenTK/Platform/Egl/EglContext.cs @@ -60,7 +60,12 @@ 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; + } Mode = new EglGraphicsMode().SelectGraphicsMode(window, mode.ColorFormat, mode.Depth, mode.Stencil, mode.Samples, mode.AccumulatorFormat, mode.Buffers, mode.Stereo, @@ -76,6 +81,13 @@ namespace OpenTK.Platform.Egl HandleAsEGLContext = Egl.CreateContext(window.Display, config, shared != null ? shared.HandleAsEGLContext : IntPtr.Zero, attrib_list); MakeCurrent(window); + + RenderApi api = (Renderable & RenderableFlags.GL) != 0 ? RenderApi.GL : RenderApi.ES; + Debug.Print("[EGL] Binding rendering API {0}.", api); + if (!Egl.BindAPI(api)) + { + Debug.Print("[EGL] Failed to bind rendering API. Error: {0}", Egl.GetError()); + } } public EglContext(ContextHandle handle, EglWindowInfo window, IGraphicsContext sharedContext, diff --git a/Source/OpenTK/Platform/Egl/EglUnixContext.cs b/Source/OpenTK/Platform/Egl/EglUnixContext.cs index 5880138e..5ceca2d1 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 GL = OpenTK.Platform.X11.DL.Open("libGL", 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); public EglUnixContext(GraphicsMode mode, EglWindowInfo window, IGraphicsContext sharedContext, int major, int minor, GraphicsContextFlags flags) @@ -59,11 +61,19 @@ 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; } protected override void Dispose(bool manual) { + if (GL != IntPtr.Zero) + { + X11.DL.Close(GL); + } if (ES1 != IntPtr.Zero) { X11.DL.Close(ES1); @@ -73,7 +83,27 @@ namespace OpenTK.Platform.Egl X11.DL.Close(ES2); } + 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); + } } } From 19b34446bbbc4e259e49ce755142b03677631769 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 26 Jun 2014 15:39:07 +0200 Subject: [PATCH 07/41] [ES][GL] Print name when loading bindings (debug only) --- Source/OpenTK/Graphics/GraphicsBindingsBase.cs | 3 +++ 1 file changed, 3 insertions(+) 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(); From 753032b84424f212becbb9de7ff97a3a0a67cab9 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 26 Jun 2014 15:40:00 +0200 Subject: [PATCH 08/41] [KMS] Added DRM/GBM framebuffer implementation --- Source/OpenTK/OpenTK.csproj | 2 + Source/OpenTK/Platform/Egl/EglContext.cs | 19 +- Source/OpenTK/Platform/Egl/EglUnixContext.cs | 10 +- Source/OpenTK/Platform/Linux/Bindings/Drm.cs | 105 +++++-- Source/OpenTK/Platform/Linux/Bindings/Gbm.cs | 41 ++- Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 2 +- Source/OpenTK/Platform/Linux/Bindings/Poll.cs | 65 ++++ .../Platform/Linux/LinuxDisplayDriver.cs | 131 +++++--- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 66 +++- .../Platform/Linux/LinuxGraphicsContext.cs | 297 ++++++++++++++++++ .../Platform/Linux/LinuxNativeWindow.cs | 17 +- .../OpenTK/Platform/Linux/LinuxWindowInfo.cs | 11 +- 12 files changed, 651 insertions(+), 115 deletions(-) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Poll.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 794abfa5..4c2c827d 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -813,6 +813,8 @@ + + diff --git a/Source/OpenTK/Platform/Egl/EglContext.cs b/Source/OpenTK/Platform/Egl/EglContext.cs index ee9d4939..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. @@ -66,6 +66,14 @@ namespace OpenTK.Platform.Egl { 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, @@ -81,13 +89,6 @@ namespace OpenTK.Platform.Egl HandleAsEGLContext = Egl.CreateContext(window.Display, config, shared != null ? shared.HandleAsEGLContext : IntPtr.Zero, attrib_list); MakeCurrent(window); - - RenderApi api = (Renderable & RenderableFlags.GL) != 0 ? RenderApi.GL : RenderApi.ES; - Debug.Print("[EGL] Binding rendering API {0}.", api); - if (!Egl.BindAPI(api)) - { - Debug.Print("[EGL] Failed to bind rendering API. Error: {0}", Egl.GetError()); - } } public EglContext(ContextHandle handle, EglWindowInfo window, IGraphicsContext sharedContext, diff --git a/Source/OpenTK/Platform/Egl/EglUnixContext.cs b/Source/OpenTK/Platform/Egl/EglUnixContext.cs index 5ceca2d1..94eb2e43 100644 --- a/Source/OpenTK/Platform/Egl/EglUnixContext.cs +++ b/Source/OpenTK/Platform/Egl/EglUnixContext.cs @@ -35,9 +35,9 @@ namespace OpenTK.Platform.Egl { class EglUnixContext : EglContext { - IntPtr GL = OpenTK.Platform.X11.DL.Open("libGL", 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) @@ -70,10 +70,6 @@ namespace OpenTK.Platform.Egl protected override void Dispose(bool manual) { - if (GL != IntPtr.Zero) - { - X11.DL.Close(GL); - } if (ES1 != IntPtr.Zero) { X11.DL.Close(ES1); @@ -82,6 +78,10 @@ namespace OpenTK.Platform.Egl { X11.DL.Close(ES2); } + if (GL != IntPtr.Zero) + { + X11.DL.Close(GL); + } GL = ES1 = ES2 = IntPtr.Zero; diff --git a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs index 7f2bf590..06f43d61 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -32,12 +32,37 @@ 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 = "drmModeGetCrtc", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ModeGetCrtc(int fd, uint crtcId); + public static extern IntPtr ModeGetCrtc(int fd, int crtcId); [DllImport(lib, EntryPoint = "drmModeFreeConnector", CallingConvention = CallingConvention.Cdecl)] public static extern void ModeFreeConnector(IntPtr ptr); @@ -46,27 +71,31 @@ namespace OpenTK.Platform.Linux public static extern void ModeFreeEncoder(IntPtr ptr); [DllImport(lib, EntryPoint = "drmModeGetConnector", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ModeGetConnector(int fd, uint connector_id); + public static extern IntPtr ModeGetConnector(int fd, int connector_id); [DllImport(lib, EntryPoint = "drmModeGetEncoder", CallingConvention = CallingConvention.Cdecl)] - public static extern IntPtr ModeGetEncoder(int fd, uint encoder_id); + 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, uint crtcId, uint bufferId, - uint x, uint y, uint* connectors, int count, ModeInfo* mode); + unsafe public static extern int ModeSetCrtc(int fd, int crtcId, int bufferId, + int x, int y, int* connectors, int count, ModeInfo* mode); } - enum ModeConnection : byte + enum ModeConnection { Connected = 1, Disconnected = 2, Unknown = 3 } - enum ModeSubPixel : byte + enum ModeSubPixel { Unknown = 1, HorizontalRgb = 2, @@ -76,35 +105,53 @@ namespace OpenTK.Platform.Linux 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 uint connector_id; - public uint encoder_id; - public uint connector_type; - public uint connector_type_id; + public int connector_id; + public int encoder_id; + public int connector_type; + public int connector_type_id; public ModeConnection connection; - public uint mmWidth, mmHeight; + public int mmWidth, mmHeight; public ModeSubPixel subpixel; public int count_modes; public ModeInfo* modes; public int count_props; - public uint *props; - public ulong *prop_values; + public int *props; + public long *prop_values; public int count_encoders; - public uint *encoders; + public int *encoders; } struct ModeCrtc { - public uint crtc_id; - public uint buffer_id; + public int crtc_id; + public int buffer_id; - public uint x, y; - public uint width, height; + public int x, y; + public int width, height; public int mode_valid; public ModeInfo mode; @@ -113,11 +160,11 @@ namespace OpenTK.Platform.Linux struct ModeEncoder { - public uint encoder_id; - public uint encoder_type; - public uint crtc_id; - public uint possible_crtcs; - public uint possible_clones; + public int encoder_id; + public int encoder_type; + public int crtc_id; + public int possible_crtcs; + public int possible_clones; } [StructLayout(LayoutKind.Sequential)] @@ -127,7 +174,7 @@ namespace OpenTK.Platform.Linux public ushort hdisplay, hsync_start, hsync_end, htotal, hskew; public ushort vdisplay, vsync_start, vsync_end, vtotal, vscan; - public uint vrefresh; // refresh rate * 1000 + public int vrefresh; // refresh rate * 1000 public uint flags; public uint type; @@ -138,13 +185,13 @@ namespace OpenTK.Platform.Linux unsafe struct ModeRes { public int count_fbs; - public uint* fbs; + public int* fbs; public int count_crtcs; - public uint* crtcs; + public int* crtcs; public int count_connectors; - public uint* connectors; + public int* connectors; public int count_encoders; - public uint* encoders; + public int* encoders; public int min_width, max_width; public int min_height, max_height; } diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs index 9225e442..4c11eda6 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -32,24 +32,57 @@ using System.Runtime.InteropServices; namespace OpenTK.Platform.Linux { - using GbmDevice = IntPtr; // opaque pointer "struct gbm_device*" + using Device = IntPtr; // opaque pointer "struct gbm_device*" + using Surface = IntPtr; + using BufferObject = 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_get_device", CallingConvention = CallingConvention.Cdecl)] + public static extern Device BOGetDevice(BufferObject bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_handle", CallingConvention = CallingConvention.Cdecl)] + public static extern BufferObjectHandle BOGetHandle(BufferObject bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_height", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOGetHeight(BufferObject bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_width", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOGetWidth(BufferObject bo); + + [DllImport(lib, EntryPoint = "gbm_bo_get_stride", CallingConvention = CallingConvention.Cdecl)] + public static extern int BOGetStride(BufferObject bo); + + [DllImport(lib, EntryPoint = "gbm_bo_set_user_data", CallingConvention = CallingConvention.Cdecl)] + public static extern void BOSetUserData(BufferObject bo, IntPtr data, DestroyUserDataCallback callback); + [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] - public static extern GbmDevice CreateDevice(int fd); + public static extern Device CreateDevice(int fd); + + [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 IntPtr CreateSurface(GbmDevice gbm, int width, int height, SurfaceFormat format, SurfaceFlags flags); + 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(GbmDevice gbm, SurfaceFormat format, SurfaceFlags usage); + 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 diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs index c571e6d8..a4df9710 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -33,7 +33,7 @@ using System.Text; namespace OpenTK.Platform.Linux { - class Libc + partial class Libc { const string lib = "libc"; diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs new file mode 100644 index 00000000..67963695 --- /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)] + 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, + Nval = 0x20, + } + + [StructLayout(LayoutKind.Sequential)] + struct PollFD + { + public int fd; + public PollFlags events; + public PollFlags revents; + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs index a12fd06e..14f6fb82 100644 --- a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -35,53 +35,79 @@ using OpenTK.Graphics; namespace OpenTK.Platform.Linux { - class LinuxDisplayDriver : DisplayDeviceBase + // Stores platform-specific information about a display + class LinuxDisplay { - unsafe class LinuxDisplay + 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 { - public ModeConnector* Connector; - public ModeEncoder* Encoder; - public ModeCrtc* Crtc; - public ModeInfo Mode + get { - get - { - if (Crtc == null) - throw new InvalidOperationException(); + if (Crtc == IntPtr.Zero) + throw new InvalidOperationException(); - return Crtc->mode; + unsafe + { + return pCrtc->mode; } } - public ModeInfo OriginalMode; + } + */ - public int Id + public ModeInfo OriginalMode; + + public int Id + { + get { - get + if (Crtc == IntPtr.Zero) + throw new InvalidOperationException(); + + unsafe { - if (Crtc == null) - throw new InvalidOperationException(); - - return (int)Crtc->crtc_id; + return (int)pCrtc->crtc_id; } } - - public LinuxDisplay(ModeConnector* c, ModeEncoder* e, ModeCrtc* r) - { - Connector = c; - Encoder = e; - Crtc = r; - OriginalMode = Crtc->mode; // in case we change resolution later on - } } + public LinuxDisplay(IntPtr c, IntPtr e, IntPtr r) + { + 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) { - FD = fd; - QueryDisplays(); + Debug.Print("[KMS] Creating LinuxDisplayDriver for fd:{0}", fd); + Debug.Indent(); + try + { + FD = fd; + QueryDisplays(); + } + finally + { + Debug.Unindent(); + } } void QueryDisplays() @@ -109,7 +135,7 @@ namespace OpenTK.Platform.Linux if (connector != null) { if (connector->connection == ModeConnection.Connected && - connector->count_modes > 0) + connector->count_modes > 0) { // Connector found! AddDisplay(connector); @@ -176,20 +202,28 @@ namespace OpenTK.Platform.Linux return; } - LinuxDisplay display = new LinuxDisplay(c, encoder, crtc); + LinuxDisplay display = new LinuxDisplay((IntPtr)c, (IntPtr)encoder, (IntPtr)crtc); if (!DisplayIds.ContainsKey(display.Id)) { + Debug.Print("[KMS] Adding display {0} as {1}", display.Id, AvailableDevices.Count); DisplayIds.Add(display.Id, AvailableDevices.Count); } - List modes = new List(); - for (int i = 0; i < display.Connector->count_modes; i++) + int mode_count = display.pConnector->count_modes; + Debug.Print("[KMS] Display supports {0} modes", mode_count); + List modes = new List(mode_count); + for (int i = 0; i < mode_count; i++) { - ModeInfo* mode = display.Connector->modes + i; - DisplayResolution res = GetDisplayResolution(mode); - modes.Add(res); + 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.Add(res); + } } - ModeInfo current_mode = display.Mode; + ModeInfo current_mode = display.pCrtc->mode; DisplayResolution current = GetDisplayResolution(¤t_mode); // Note: since we are not running a display manager, we are free @@ -211,30 +245,27 @@ namespace OpenTK.Platform.Linux Primary = device; } - Debug.Print("[KMS] Detected display {0}", device); + Debug.Print("[KMS] Added DisplayDevice {0}", device); AvailableDevices.Add(device); } unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode) { - if (mode == null) - throw new ArgumentNullException(); - return new DisplayResolution( 0, 0, - mode->htotal, mode->vtotal, + mode->hdisplay, mode->vdisplay, 32, // This is actually part of the framebuffer, not the DisplayResolution - mode->vrefresh / 1000.0f); + mode->vrefresh); } unsafe static ModeInfo* GetModeInfo(LinuxDisplay display, DisplayResolution resolution) { - for (int i = 0; i < display.Connector->count_modes; i++) + for (int i = 0; i < display.pConnector->count_modes; i++) { - ModeInfo* mode = display.Connector->modes + i; + ModeInfo* mode = display.pConnector->modes + i; if (mode != null && - mode->htotal == resolution.Width && - mode->vtotal == resolution.Height) + mode->hdisplay == resolution.Width && + mode->vdisplay == resolution.Height) { return mode; } @@ -250,10 +281,10 @@ namespace OpenTK.Platform.Linux { LinuxDisplay display = (LinuxDisplay)device.Id; ModeInfo* mode = GetModeInfo(display, resolution); - uint connector_id = display.Connector->connector_id; + int connector_id = display.pConnector->connector_id; if (mode != null) { - return Drm.ModeSetCrtc(FD, (uint)display.Id, 0, 0, 0, + return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0, &connector_id, 1, mode) == 0; } return false; @@ -265,9 +296,9 @@ namespace OpenTK.Platform.Linux unsafe { LinuxDisplay display = (LinuxDisplay)device.Id; - uint connector_id = display.Connector->connector_id; ModeInfo mode = display.OriginalMode; - return Drm.ModeSetCrtc(FD, (uint)display.Id, 0, 0, 0, + int connector_id = display.pConnector->connector_id; + return Drm.ModeSetCrtc(FD, display.Id, 0, 0, 0, &connector_id, 1, &mode) == 0; } } diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 30661205..11e5e80b 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -29,6 +29,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using OpenTK.Graphics; using OpenTK.Input; @@ -48,21 +49,62 @@ namespace OpenTK.Platform.Linux IJoystickDriver2 JoystickDriver; IDisplayDeviceDriver DisplayDriver; + const string gpu_path = "/dev/dri"; // card0, card1, ... + public LinuxFactory() { - SetupEgl(); + // 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 + var files = Directory.GetFiles(gpu_path); + foreach (var gpu in files) + { + if (Path.GetFileName(gpu).StartsWith("card")) + { + int test_fd = SetupDisplay(gpu); + if (test_fd >= 0) + { + try + { + DisplayDriver = new LinuxDisplayDriver(test_fd); + if (DisplayDriver.GetDisplay(DisplayIndex.Primary) != 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(); + } } #region Private Members - void SetupEgl() + int SetupDisplay(string gpu) { - // Todo: support multi-GPU systems - string gpu = "/dev/dri/card0"; - fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); + Debug.Print("[KMS] Attempting to use gpu '{0}'.", gpu); + + int fd = Libc.open(gpu, OpenFlags.ReadWrite | OpenFlags.CloseOnExec); if (fd < 0) { - throw new NotSupportedException("[KMS] No KMS-capable GPU available"); + Debug.Print("[KMS] Failed to open gpu"); + return fd; } Debug.Print("[KMS] GPU '{0}' opened as fd:{1}", gpu, fd); @@ -87,6 +129,8 @@ namespace OpenTK.Platform.Linux 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, display); + + return fd; } #endregion @@ -95,21 +139,17 @@ namespace OpenTK.Platform.Linux public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) { - return new LinuxNativeWindow(display, gbm_device, x, y, width, height, title, mode, options, display_device); + return new LinuxNativeWindow(display, gbm_device, fd, x, y, width, height, title, mode, options, display_device); } public override IDisplayDeviceDriver CreateDisplayDeviceDriver() { - lock (this) - { - DisplayDriver = DisplayDriver ?? new LinuxDisplayDriver(fd); - return DisplayDriver; - } + return DisplayDriver; } public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) { - return new EglUnixContext(mode, (EglWindowInfo)window, shareContext, major, minor, flags); + return new LinuxGraphicsContext(mode, (LinuxWindowInfo)window, shareContext, major, minor, flags); } public override GraphicsContext.GetCurrentContextDelegate CreateGetCurrentGraphicsContext() diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs new file mode 100644 index 00000000..f42d1708 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -0,0 +1,297 @@ +#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 + { + IntPtr 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) + { + 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); + } + } + } + + IntPtr LockSurface() + { + IntPtr gbm_surface = WindowInfo.Handle; + return Gbm.LockFrontBuffer(gbm_surface); + } + + int GetFramebuffer(IntPtr bo) + { + if (bo == IntPtr.Zero) + goto fail; + + int bo_handle = Gbm.BOGetHandle(bo).ToInt32(); + if (bo_handle == 0) + { + Debug.Print("[KMS] Gbm.BOGetHandle({0:x}) failed.", bo); + goto fail; + } + + int width = Gbm.BOGetWidth(bo); + int height = Gbm.BOGetHeight(bo); + int bpp = Mode.ColorFormat.BitsPerPixel; + int depth = Mode.Depth; + int stride = Gbm.BOGetStride(bo); + + 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; + } + + Gbm.BOSetUserData(bo, (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(IntPtr bo, IntPtr data) + { + IntPtr gbm = Gbm.BOGetDevice(bo); + 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->width, + wnd.DisplayDevice.pCrtc->height, + &connector_id, + 1, + &mode); + } + } + } + base.Dispose(manual); + } + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index ee3bf89a..16ae7716 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -47,7 +47,7 @@ namespace OpenTK.Platform.Linux Rectangle bounds; Size client_size; - public LinuxNativeWindow(IntPtr display, IntPtr gbm, + 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) @@ -58,7 +58,8 @@ namespace OpenTK.Platform.Linux bounds = new Rectangle(0, 0, width, height); client_size = bounds.Size; - window = new LinuxWindowInfo(display); + window = new LinuxWindowInfo(display, fd, display_device.Id as LinuxDisplay); + if (!mode.Index.HasValue) { mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0); @@ -69,7 +70,17 @@ namespace OpenTK.Platform.Linux SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; if (!Gbm.IsFormatSupported(gbm, format, usage)) { - //format = SurfaceFormat.xrgba; + Debug.Print("[KMS] Failed to find suitable surface format, using XRGB8888"); + format = SurfaceFormat.XRGB8888; + } + + // 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. + if (display_device != null) + { + width = display_device.Width; + height = display_device.Height; } Debug.Print("[KMS] Creating GBM surface on {0:x} with {1}x{2} {3} [{4}]", diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs index 0a1f0933..b6df115a 100644 --- a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -28,15 +28,24 @@ #endregion using System; +using System.Diagnostics; using OpenTK.Platform.Egl; namespace OpenTK.Platform.Linux { class LinuxWindowInfo : EglWindowInfo { - public LinuxWindowInfo(IntPtr display) + public int FD { get; private set; } + public LinuxDisplay DisplayDevice { get; private set; } + + public LinuxWindowInfo(IntPtr display, int fd, LinuxDisplay display_device) : base(IntPtr.Zero, display, IntPtr.Zero) { + if (display_device == null) + throw new ArgumentNullException(); + + FD = fd; + DisplayDevice = display_device; // The window handle and surface handle must // be filled in manually once they are known. } From 452bafdbb1ea86164875455f2da4ca4225d52bde Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 26 Jun 2014 21:46:04 +0200 Subject: [PATCH 09/41] [KMS] Fixed NRE in Toolkit.Init(); improved resource cleanup --- Source/OpenTK/Platform/Linux/Bindings/Drm.cs | 3 + Source/OpenTK/Platform/Linux/Bindings/Gbm.cs | 3 + .../Platform/Linux/LinuxDisplayDriver.cs | 208 +++++++++++++----- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 29 ++- .../Platform/Linux/LinuxGraphicsContext.cs | 2 + 5 files changed, 189 insertions(+), 56 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs index 06f43d61..c0704837 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -61,6 +61,9 @@ namespace OpenTK.Platform.Linux [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); diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs index 4c11eda6..05e1ffd9 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -65,6 +65,9 @@ namespace OpenTK.Platform.Linux [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] public static extern Device CreateDevice(int fd); + [DllImport(lib, EntryPoint = "gbm_destroy_device", 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); diff --git a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs index 14f6fb82..ad38c523 100644 --- a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -102,7 +102,7 @@ namespace OpenTK.Platform.Linux try { FD = fd; - QueryDisplays(); + UpdateDisplays(fd); } finally { @@ -110,7 +110,76 @@ namespace OpenTK.Platform.Linux } } - void QueryDisplays() + /// \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 { @@ -119,33 +188,12 @@ namespace OpenTK.Platform.Linux AvailableDevices.Clear(); DisplayIds.Clear(); - ModeRes* resources = (ModeRes*)Drm.ModeGetResources(FD); - if (resources == null) + List displays = new List(); + if (QueryDisplays(fd, displays)) { - throw new NotSupportedException("[KMS] DRM ModeGetResources failed"); - } - 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) + foreach (LinuxDisplay display in displays) { - if (connector->connection == ModeConnection.Connected && - connector->count_modes > 0) - { - // Connector found! - AddDisplay(connector); - } - else - { - // This is not the display we are looking for - Drm.ModeFreeConnector((IntPtr)connector); - connector = null; - } + AddDisplay(display); } } @@ -157,14 +205,13 @@ namespace OpenTK.Platform.Linux } } - unsafe void AddDisplay(ModeConnector* c) + unsafe static ModeEncoder* GetEncoder(int fd, ModeConnector* c) { - // Find corresponding encoder ModeEncoder* encoder = null; for (int i = 0; i < c->count_encoders && encoder == null; i++) { ModeEncoder* e = (ModeEncoder*)Drm.ModeGetEncoder( - FD, *(c->encoders + i)); + fd, *(c->encoders + i)); if (e != null) { if (e->encoder_id == c->encoder_id) @@ -186,10 +233,14 @@ namespace OpenTK.Platform.Linux else { Debug.Print("[KMS] Failed to find encoder for connector {0}", c->connector_id); - return; } - ModeCrtc* crtc = (ModeCrtc*)Drm.ModeGetCrtc(FD, encoder->crtc_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}", @@ -199,19 +250,14 @@ namespace OpenTK.Platform.Linux { Debug.Print("[KMS] Failed to find crtc {0} for encoder {1}", encoder->crtc_id, encoder->encoder_id); - return; - } - - LinuxDisplay display = new LinuxDisplay((IntPtr)c, (IntPtr)encoder, (IntPtr)crtc); - if (!DisplayIds.ContainsKey(display.Id)) - { - Debug.Print("[KMS] Adding display {0} as {1}", display.Id, AvailableDevices.Count); - DisplayIds.Add(display.Id, AvailableDevices.Count); } + 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} modes", mode_count); - List modes = new List(mode_count); for (int i = 0; i < mode_count; i++) { ModeInfo* mode = display.pConnector->modes + i; @@ -220,33 +266,89 @@ namespace OpenTK.Platform.Linux Debug.Print("Mode {0}: {1}x{2} @{3}", i, mode->hdisplay, mode->vdisplay, mode->vrefresh); DisplayResolution res = GetDisplayResolution(mode); - modes.Add(res); + modes[i] = res; } } - ModeInfo current_mode = display.pCrtc->mode; - DisplayResolution current = GetDisplayResolution(¤t_mode); + 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. - System.Drawing.Rectangle bounds = - new System.Drawing.Rectangle( - AvailableDevices.Count == 0 ? 0 : AvailableDevices[0].Bounds.Right, - 0, - current.Width, - current.Height); + 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((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, bounds, display); + modes, GetBounds(current), display); - if (AvailableDevices.Count == 0) + if (is_primary) { Primary = device; } + UpdateDisplayIndices(display, device); + Debug.Print("[KMS] Added DisplayDevice {0}", device); - AvailableDevices.Add(device); } unsafe static DisplayResolution GetDisplayResolution(ModeInfo* mode) diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 11e5e80b..45315d85 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -69,8 +69,7 @@ namespace OpenTK.Platform.Linux { try { - DisplayDriver = new LinuxDisplayDriver(test_fd); - if (DisplayDriver.GetDisplay(DisplayIndex.Primary) != null) + if (LinuxDisplayDriver.QueryDisplays(test_fd, null)) { fd = test_fd; break; @@ -135,6 +134,30 @@ namespace OpenTK.Platform.Linux #endregion + #region Protected Members + + protected override void Dispose(bool manual) + { + if (display != IntPtr.Zero) + { + Egl.Terminate(display); + display = IntPtr.Zero; + } + if (gbm_device != IntPtr.Zero) + { + Gbm.DestroyDevice(gbm_device); + gbm_device = IntPtr.Zero; + } + if (fd >= 0) + { + 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) @@ -144,7 +167,7 @@ namespace OpenTK.Platform.Linux public override IDisplayDeviceDriver CreateDisplayDeviceDriver() { - return DisplayDriver; + return new LinuxDisplayDriver(fd); } public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs index f42d1708..eaa6be42 100644 --- a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -295,3 +295,5 @@ namespace OpenTK.Platform.Linux } } + + From 69dbdb7d67c76c1a5c760f80ed93989df93e645c Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 26 Jun 2014 21:51:01 +0200 Subject: [PATCH 10/41] [KMS] Fixed Gbm.DestroyDevice entry point --- Source/OpenTK/Platform/Linux/Bindings/Gbm.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs index 05e1ffd9..efde6e8b 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -65,7 +65,7 @@ namespace OpenTK.Platform.Linux [DllImport(lib, EntryPoint = "gbm_create_device", CallingConvention = CallingConvention.Cdecl)] public static extern Device CreateDevice(int fd); - [DllImport(lib, EntryPoint = "gbm_destroy_device", CallingConvention = CallingConvention.Cdecl)] + [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)] From 9bc774f78c7286639a91b4206a3782a6f479e758 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 26 Jun 2014 22:52:17 +0200 Subject: [PATCH 11/41] [KMS] Query GPUs lazily --- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 75 ++++++++++++++------ 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 45315d85..f9d9c0a9 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -42,16 +42,38 @@ namespace OpenTK.Platform.Linux // Linux KMS platform class LinuxFactory : PlatformFactoryBase { - int fd; + int _fd; IntPtr gbm_device; - IntPtr display; + IntPtr egl_display; IJoystickDriver2 JoystickDriver; - IDisplayDeviceDriver DisplayDriver; + IKeyboardDriver2 KeyboardDriver; 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 @@ -59,12 +81,16 @@ namespace OpenTK.Platform.Linux // 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); + int test_fd = SetupDisplay(gpu, out gbm_device, out egl_display); if (test_fd >= 0) { try @@ -91,13 +117,16 @@ namespace OpenTK.Platform.Linux Debug.Print("[Error] No valid GPU found, bailing out."); throw new PlatformNotSupportedException(); } + + return fd; } - #region Private Members - - int SetupDisplay(string gpu) + 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) @@ -114,20 +143,20 @@ namespace OpenTK.Platform.Linux } Debug.Print("[KMS] GBM {0:x} created successfully; ", gbm_device); - display = Egl.GetDisplay(gbm_device); - if (display == IntPtr.Zero) + 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", display); + Debug.Print("[KMS] EGL display {0:x} created successfully", egl_display); int major, minor; - if (!Egl.Initialize(display, out major, out 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, display); + Debug.Print("[KMS] EGL {0}.{1} initialized successfully on display {2:x}", major, minor, egl_display); return fd; } @@ -138,19 +167,19 @@ namespace OpenTK.Platform.Linux protected override void Dispose(bool manual) { - if (display != IntPtr.Zero) + if (egl_display != IntPtr.Zero) { - Egl.Terminate(display); - display = IntPtr.Zero; + Egl.Terminate(egl_display); + egl_display = IntPtr.Zero; } if (gbm_device != IntPtr.Zero) { Gbm.DestroyDevice(gbm_device); gbm_device = IntPtr.Zero; } - if (fd >= 0) + if (_fd >= 0) { - Libc.close(fd); + Libc.close(_fd); } base.Dispose(manual); @@ -162,12 +191,12 @@ namespace OpenTK.Platform.Linux public override INativeWindow CreateNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) { - return new LinuxNativeWindow(display, gbm_device, fd, x, y, width, height, title, mode, options, 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(fd); + return new LinuxDisplayDriver(gpu_fd); } public override IGraphicsContext CreateGLContext(GraphicsMode mode, IWindowInfo window, IGraphicsContext shareContext, bool directRendering, int major, int minor, GraphicsContextFlags flags) @@ -185,7 +214,13 @@ namespace OpenTK.Platform.Linux public override IKeyboardDriver2 CreateKeyboardDriver() { - throw new NotImplementedException(); + lock (this) + { + KeyboardDriver = KeyboardDriver ?? + (IKeyboardDriver2)new LinuxKeyboardLibInput() ?? + (IKeyboardDriver2)new LinuxKeyboardTTY(); + return KeyboardDriver; + } } public override IMouseDriver2 CreateMouseDriver() From 97a539258bd7ec413969da71bf4a57e422cd216c Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 26 Jun 2014 23:11:12 +0200 Subject: [PATCH 12/41] [Linux] Added stub TTY and libinput IKeyboardDriver2 --- Source/OpenTK/OpenTK.csproj | 4 + .../Platform/Linux/Bindings/LibInput.cs | 92 +++++++++++++++++++ Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 7 ++ Source/OpenTK/Platform/Linux/Bindings/Udev.cs | 43 +++++++++ .../Platform/Linux/LinuxKeyboardLibInput.cs | 77 ++++++++++++++++ .../OpenTK/Platform/Linux/LinuxKeyboardTTY.cs | 64 +++++++++++++ .../Platform/Linux/LinuxNativeWindow.cs | 5 + 7 files changed, 292 insertions(+) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/LibInput.cs create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Udev.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 4c2c827d..b8a47635 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -815,6 +815,10 @@ + + + + diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs new file mode 100644 index 00000000..aadf4aaf --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -0,0 +1,92 @@ +#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 + +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 + { + 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); + } + + [StructLayout(LayoutKind.Sequential)] + class InputInterface + { + IntPtr open; + 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); + } + + #region Default implementation + + static CloseRestrictedCallback CloseRestricted = CloseRestrictedHandler; + static void CloseRestrictedHandler(int fd, IntPtr data) + { + Debug.Print("[Input] Closing fd {0}", fd); + Libc.close(fd); + } + + static OpenRestrictedCallback OpenRestricted = OpenRestrictedHandler; + static int OpenRestrictedHandler(IntPtr path, int flags, IntPtr data) + { + Debug.Print("[Input] Opening path '{0}'", Marshal.PtrToStringAnsi(path)); + return Libc.open(path, (OpenFlags)flags); + } + + public static readonly InputInterface Default = new InputInterface( + OpenRestricted, CloseRestricted); + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs index a4df9710..fc77953d 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -49,11 +49,18 @@ namespace OpenTK.Platform.Linux [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); + + [DllImport(lib)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool isatty(int fd); } [Flags] diff --git a/Source/OpenTK/Platform/Linux/Bindings/Udev.cs b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs new file mode 100644 index 00000000..0f674f5a --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs @@ -0,0 +1,43 @@ +#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")] + public static extern IntPtr New(); + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs new file mode 100644 index 00000000..d81ceb66 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs @@ -0,0 +1,77 @@ +#region License +// +// LinuxKeyboardLibInput.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 OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + class LinuxKeyboardLibInput : IKeyboardDriver2 + { + IntPtr udev; + IntPtr input_context; + InputInterface input_interface = InputInterface.Default; + + public LinuxKeyboardLibInput() + { + // Todo: add static path fallback when udev is not installed. + udev = Udev.New(); + if (udev == IntPtr.Zero) + throw new NotSupportedException("[Input] Udev.New() failed."); + + input_context = LibInput.CreateContext(input_interface, + IntPtr.Zero, udev, "seat0"); + if (input_context == IntPtr.Zero) + throw new NotSupportedException("[Input] Udev.New() failed."); + + + } + + #region IKeyboardDriver2 implementation + + public KeyboardState GetState() + { + throw new NotImplementedException(); + } + + public KeyboardState GetState(int index) + { + throw new NotImplementedException(); + } + + public string GetDeviceName(int index) + { + throw new NotImplementedException(); + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs new file mode 100644 index 00000000..2d3c848b --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs @@ -0,0 +1,64 @@ +#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 OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + class LinuxKeyboardTTY : IKeyboardDriver2 + { + public LinuxKeyboardTTY() + { + } + + #region IKeyboardDriver2 implementation + + public KeyboardState GetState() + { + throw new NotImplementedException(); + } + + public KeyboardState GetState(int index) + { + throw new NotImplementedException(); + } + + public string GetDeviceName(int index) + { + throw new NotImplementedException(); + } + + #endregion + + + + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index 16ae7716..eba4214c 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -146,6 +146,11 @@ namespace OpenTK.Platform.Linux #region INativeWindow Members + public override void ProcessEvents() + { + base.ProcessEvents(); + } + public override void Close() { exists = false; From 9e73358dd7904717f594c730ca413903fcba1d1a Mon Sep 17 00:00:00 2001 From: thefiddler Date: Mon, 14 Jul 2014 15:42:17 +0000 Subject: [PATCH 13/41] [Linux] Implemented TTY and libinput keyboard drivers --- Source/OpenTK/OpenTK.csproj | 3 +- .../Platform/Linux/Bindings/LibInput.cs | 77 ++- Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 34 ++ Source/OpenTK/Platform/Linux/Bindings/Poll.cs | 2 +- .../Platform/Linux/Bindings/Terminal.cs | 166 ++++++ Source/OpenTK/Platform/Linux/Bindings/Udev.cs | 5 +- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 2 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 226 ++++++++ .../Platform/Linux/LinuxKeyboardLibInput.cs | 77 --- .../OpenTK/Platform/Linux/LinuxKeyboardTTY.cs | 485 +++++++++++++++++- 10 files changed, 970 insertions(+), 107 deletions(-) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Terminal.cs create mode 100644 Source/OpenTK/Platform/Linux/LinuxInput.cs delete mode 100644 Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index b8a47635..76514e64 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -817,8 +817,9 @@ - + + diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index aadf4aaf..b5cdd9ef 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -27,7 +27,7 @@ // #endregion -#pragma warning disable 0169 +#pragma warning disable 0169, 0219 using System; using System.Diagnostics; @@ -48,6 +48,60 @@ namespace OpenTK.Platform.Linux [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_dispatch", CallingConvention = CallingConvention.Cdecl)] + public static extern int Dispatch(IntPtr libinput); + + [DllImport(lib, EntryPoint = "libinput_get_event", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr GetEvent(IntPtr libinput); + + [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); + } + + 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 } [StructLayout(LayoutKind.Sequential)] @@ -66,27 +120,6 @@ namespace OpenTK.Platform.Linux open = Marshal.GetFunctionPointerForDelegate(open_restricted); close = Marshal.GetFunctionPointerForDelegate(close_restricted); } - - #region Default implementation - - static CloseRestrictedCallback CloseRestricted = CloseRestrictedHandler; - static void CloseRestrictedHandler(int fd, IntPtr data) - { - Debug.Print("[Input] Closing fd {0}", fd); - Libc.close(fd); - } - - static OpenRestrictedCallback OpenRestricted = OpenRestrictedHandler; - static int OpenRestrictedHandler(IntPtr path, int flags, IntPtr data) - { - Debug.Print("[Input] Opening path '{0}'", Marshal.PtrToStringAnsi(path)); - return Libc.open(path, (OpenFlags)flags); - } - - public static readonly InputInterface Default = new InputInterface( - OpenRestricted, CloseRestricted); - - #endregion } } diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs index fc77953d..264bab2a 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -46,6 +46,12 @@ namespace OpenTK.Platform.Linux [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); @@ -58,6 +64,28 @@ namespace OpenTK.Platform.Linux [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); @@ -94,6 +122,12 @@ namespace OpenTK.Platform.Linux 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 { diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs index 67963695..a048398d 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs @@ -34,7 +34,7 @@ namespace OpenTK.Platform.Linux { partial class Libc { - [DllImport(lib)] + [DllImport(lib, CallingConvention = CallingConvention.Cdecl)] 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) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs new file mode 100644 index 00000000..8ed7257c --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs @@ -0,0 +1,166 @@ +#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 = "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 index 0f674f5a..4c341b70 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Udev.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Udev.cs @@ -36,8 +36,11 @@ namespace OpenTK.Platform.Linux { const string lib = "libudev"; - [DllImport(lib, EntryPoint = "udev_new")] + [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/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index f9d9c0a9..8d0d8e27 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -217,7 +217,7 @@ namespace OpenTK.Platform.Linux lock (this) { KeyboardDriver = KeyboardDriver ?? - (IKeyboardDriver2)new LinuxKeyboardLibInput() ?? + // Todo: use LinuxInput driver if available? (IKeyboardDriver2)new LinuxKeyboardTTY(); return KeyboardDriver; } diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs new file mode 100644 index 00000000..19081c9e --- /dev/null +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -0,0 +1,226 @@ +#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.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using OpenTK.Input; + +namespace OpenTK.Platform.Linux +{ + class LinuxInput : IKeyboardDriver2, IMouseDriver2, IDisposable + { + 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); + + input_thread = new Thread(ProcessEvents); + input_thread.IsBackground = true; + + // Todo: add static path fallback when udev is not installed. + udev = Udev.New(); + if (udev == IntPtr.Zero) + { + throw new NotSupportedException("[Input] Udev.New() failed."); + } + + input_context = LibInput.CreateContext(input_interface, + IntPtr.Zero, udev, "seat0"); + if (input_context == IntPtr.Zero) + { + throw new NotSupportedException( + String.Format("[Input] LibInput.CreateContext({0:x}) failed.", udev)); + } + + fd = LibInput.GetFD(input_context); + if (fd < 0) + { + throw new NotSupportedException( + String.Format("[Input] LibInput.GetFD({0:x}) failed.", input_context)); + } + + input_thread.Start(); + } + + #region Private Members + + static CloseRestrictedCallback CloseRestricted = CloseRestrictedHandler; + static void CloseRestrictedHandler(int fd, IntPtr data) + { + Debug.Print("[Input] Closing fd {0}", fd); + Libc.close(fd); + } + + 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); + + return fd; + } + + void ProcessEvents() + { + PollFD poll_fd = new PollFD(); + poll_fd.fd = fd; + poll_fd.events = PollFlags.In; + + while (Interlocked.Read(ref exit) == 0) + { + int ret = Libc.poll(ref poll_fd, 1, -1); + if (ret > 0 && (poll_fd.revents & PollFlags.In) != 0) + { + // Data available + ret = LibInput.Dispatch(input_context); + if (ret != 0) + { + Debug.Print("[Input] LibInput.Dispatch({0:x}) failed. Error: {1}", + input_context, ret); + continue; + } + + IntPtr pevent = LibInput.GetEvent(input_context); + if (pevent == IntPtr.Zero) + { + Debug.Print("[Input] LibInput.GetEvent({0:x}) failed.", + input_context); + continue; + } + + InputEventType type = LibInput.GetEventType(pevent); + Debug.Print(type.ToString()); + + LibInput.DestroyEvent(pevent); + } + else if (ret < 0) + { + // An error has occurred + Debug.Print("[Input] Exiting input thread {0} [ret:{1} events:{2}]", + input_thread.ManagedThreadId, ret, poll_fd.revents); + Interlocked.Increment(ref exit); + } + } + } + + #endregion + + #region IKeyboardDriver2 implementation + + KeyboardState IKeyboardDriver2.GetState() + { + return new KeyboardState(); + } + + KeyboardState IKeyboardDriver2.GetState(int index) + { + return new KeyboardState(); + } + + string IKeyboardDriver2.GetDeviceName(int index) + { + return String.Empty; + } + + #endregion + + #region IMouseDriver2 implementation + + MouseState IMouseDriver2.GetState() + { + throw new NotImplementedException(); + } + + MouseState IMouseDriver2.GetState(int index) + { + throw new NotImplementedException(); + } + + void IMouseDriver2.SetPosition(double x, double y) + { + throw new NotImplementedException(); + } + + MouseState IMouseDriver2.GetCursorState() + { + throw new NotImplementedException(); + } + + #endregion + + #region IDisposable implementation + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void Dispose(bool disposing) + { + if (disposing) + { + if (input_context != IntPtr.Zero) + { + LibInput.Suspend(input_context); + Interlocked.Increment(ref exit); + + LibInput.DestroyContext(input_context); + input_context = IntPtr.Zero; + } + + if (udev != IntPtr.Zero) + { + Udev.Destroy(udev); + udev = IntPtr.Zero; + } + + input_interface = null; + } + } + + ~LinuxInput() + { + Dispose(false); + } + + #endregion + } +} + diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs deleted file mode 100644 index d81ceb66..00000000 --- a/Source/OpenTK/Platform/Linux/LinuxKeyboardLibInput.cs +++ /dev/null @@ -1,77 +0,0 @@ -#region License -// -// LinuxKeyboardLibInput.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 OpenTK.Input; - -namespace OpenTK.Platform.Linux -{ - class LinuxKeyboardLibInput : IKeyboardDriver2 - { - IntPtr udev; - IntPtr input_context; - InputInterface input_interface = InputInterface.Default; - - public LinuxKeyboardLibInput() - { - // Todo: add static path fallback when udev is not installed. - udev = Udev.New(); - if (udev == IntPtr.Zero) - throw new NotSupportedException("[Input] Udev.New() failed."); - - input_context = LibInput.CreateContext(input_interface, - IntPtr.Zero, udev, "seat0"); - if (input_context == IntPtr.Zero) - throw new NotSupportedException("[Input] Udev.New() failed."); - - - } - - #region IKeyboardDriver2 implementation - - public KeyboardState GetState() - { - throw new NotImplementedException(); - } - - public KeyboardState GetState(int index) - { - throw new NotImplementedException(); - } - - public string GetDeviceName(int index) - { - throw new NotImplementedException(); - } - - #endregion - } -} - diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs index 2d3c848b..1ba8d14a 100644 --- a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs @@ -28,37 +28,514 @@ #endregion using System; +using System.Diagnostics; +using System.IO; +using System.Threading; using OpenTK.Input; namespace OpenTK.Platform.Linux { - class LinuxKeyboardTTY : IKeyboardDriver2 + 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); + public LinuxKeyboardTTY() { + 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) + { + 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)) + { + 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]; + } + + #region KeyMap + + 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, // Zzenkakuhankaku + 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 + + #endregion + #region IKeyboardDriver2 implementation public KeyboardState GetState() { - throw new NotImplementedException(); + lock (this) + { + return state; + } } public KeyboardState GetState(int index) { - throw new NotImplementedException(); + lock (this) + { + if (index == 0) + return state; + else + return new KeyboardState(); + } } public string GetDeviceName(int index) { - throw new NotImplementedException(); + 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 } } From e7bd311fbd33cba30622abc7e7ba2c9c5a62dddb Mon Sep 17 00:00:00 2001 From: thefiddler Date: Mon, 14 Jul 2014 21:07:28 +0000 Subject: [PATCH 14/41] [Linux] Improve checks for TTY --- Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 6 ++++++ .../OpenTK/Platform/Linux/Bindings/Terminal.cs | 4 ++++ .../OpenTK/Platform/Linux/LinuxKeyboardTTY.cs | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs index 264bab2a..06673659 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -37,6 +37,12 @@ namespace OpenTK.Platform.Linux { 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); diff --git a/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs index 8ed7257c..e72ad741 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Terminal.cs @@ -36,6 +36,10 @@ namespace OpenTK.Platform.Linux { 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); diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs index 1ba8d14a..d9c0c6bd 100644 --- a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs @@ -47,9 +47,12 @@ namespace OpenTK.Platform.Linux 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(); @@ -64,6 +67,16 @@ namespace OpenTK.Platform.Linux 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) { @@ -83,7 +96,7 @@ namespace OpenTK.Platform.Linux // Update terminal state current_state = original_state; - current_state.LocalMode &= ~(LocalFlags.ECHO | LocalFlags.ICANON | LocalFlags.ISIG); + current_state.LocalMode &= ~(/*LocalFlags.ECHO |*/ LocalFlags.ICANON | LocalFlags.ISIG); current_state.InputMode &= ~( InputFlags.ISTRIP | InputFlags.IGNCR | InputFlags.ICRNL | InputFlags.INLCR | InputFlags.IXOFF | InputFlags.IXON); @@ -113,6 +126,8 @@ namespace OpenTK.Platform.Linux { 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); From 4a53a5511ab61d589b6260b8a761b17fc0a3c782 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Mon, 14 Jul 2014 21:35:30 +0000 Subject: [PATCH 15/41] [Linux] Disabled TTY keyboard driver in favor of libinput The TTY keyboard driver requires a robust cleanup method to avoid hogging the keyboard/console after the process exists. Without this, it does not make sense to use enable this driver. --- Source/Examples/Main.cs | 17 +++++++++++++++++ Source/OpenTK/Platform/Linux/LinuxFactory.cs | 5 ++--- .../OpenTK/Platform/Linux/LinuxKeyboardTTY.cs | 6 ++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Source/Examples/Main.cs b/Source/Examples/Main.cs index eb86bd06..c23e99b5 100644 --- a/Source/Examples/Main.cs +++ b/Source/Examples/Main.cs @@ -101,6 +101,23 @@ namespace Examples public static void Main(string[] args) { Trace.Listeners.Add(new ConsoleTraceListener()); + using (Toolkit.Init()) + { + while (true) + { + var state = OpenTK.Input.Keyboard.GetState(); + if (!state.IsConnected) + { + break; + } + else if (state.IsKeyDown(OpenTK.Input.Key.Escape)) + { + break; + } + } + } + return; + Tests.GameWindowStates.Main(); return; diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 8d0d8e27..943e9dcd 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -48,6 +48,7 @@ namespace OpenTK.Platform.Linux IJoystickDriver2 JoystickDriver; IKeyboardDriver2 KeyboardDriver; + IMouseDriver2 MouseDriver; const string gpu_path = "/dev/dri"; // card0, card1, ... @@ -216,9 +217,7 @@ namespace OpenTK.Platform.Linux { lock (this) { - KeyboardDriver = KeyboardDriver ?? - // Todo: use LinuxInput driver if available? - (IKeyboardDriver2)new LinuxKeyboardTTY(); + KeyboardDriver = KeyboardDriver ?? new LinuxInput(); return KeyboardDriver; } } diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs index d9c0c6bd..63b80952 100644 --- a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs @@ -35,6 +35,11 @@ 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 @@ -552,5 +557,6 @@ namespace OpenTK.Platform.Linux #endregion } + #endif } From c5abbe80307e6ecdf32b326ec8470f4128c46767 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Mon, 14 Jul 2014 23:15:44 +0000 Subject: [PATCH 16/41] [Linux] Implemented libinput keyboard input --- Source/OpenTK/OpenTK.csproj | 1 + Source/OpenTK/Platform/DeviceCollection.cs | 144 +++++++++++++++++ .../Platform/Linux/Bindings/LibInput.cs | 41 +++++ Source/OpenTK/Platform/Linux/LinuxInput.cs | 149 +++++++++++++++++- 4 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 Source/OpenTK/Platform/DeviceCollection.cs diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 76514e64..b5f973ce 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -820,6 +820,7 @@ + diff --git a/Source/OpenTK/Platform/DeviceCollection.cs b/Source/OpenTK/Platform/DeviceCollection.cs new file mode 100644 index 00000000..57306785 --- /dev/null +++ b/Source/OpenTK/Platform/DeviceCollection.cs @@ -0,0 +1,144 @@ +#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 +{ + // 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 object Sync = new object(); + 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 (!Map.ContainsKey(id)) + { + Debug.Print("Invalid DeviceCollection<{0}> id: {1}", typeof(T).FullName, id); + return; + } + + Devices[Map[id]] = default(T); + Map.Remove(id); + } + + 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/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index b5cdd9ef..c557719d 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -55,9 +55,43 @@ namespace OpenTK.Platform.Linux [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_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); @@ -77,6 +111,13 @@ namespace OpenTK.Platform.Linux public static extern void Suspend(IntPtr libinput); } + enum DeviceCapability + { + Keyboard = 0, + Mouse, + Touch + } + enum InputEventType { None = 0, diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index 19081c9e..d3853d82 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -37,6 +37,62 @@ namespace OpenTK.Platform.Linux { class LinuxInput : IKeyboardDriver2, IMouseDriver2, IDisposable { + class KeyboardDevice + { + readonly IntPtr Device; + string name; + string output; + + public KeyboardDevice(IntPtr device, int id) + { + Device = device; + Id = id; + State.SetIsConnected(true); + } + + 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 string Output + { + get + { + output = output ?? LibInput.DeviceGetOutputName(Device); + return output; + } + } + + public KeyboardState State; + } + + class MouseDevice + { + public int FD; + public string Name; + public MouseState State; + } + + DeviceCollection Keyboards = new DeviceCollection(); + DeviceCollection Mice = new DeviceCollection(); + IntPtr udev; IntPtr input_context; InputInterface input_interface = new InputInterface( @@ -102,6 +158,8 @@ namespace OpenTK.Platform.Linux poll_fd.fd = fd; poll_fd.events = PollFlags.In; + LibInput.Resume(input_context); + while (Interlocked.Read(ref exit) == 0) { int ret = Libc.poll(ref poll_fd, 1, -1); @@ -124,8 +182,23 @@ namespace OpenTK.Platform.Linux continue; } + IntPtr device = LibInput.GetDevice(pevent); InputEventType type = LibInput.GetEventType(pevent); - Debug.Print(type.ToString()); + switch (type) + { + case InputEventType.DeviceAdded: + HandleDeviceAdded(input_context, device); + break; + + case InputEventType.DeviceRemoved: + HandleDeviceRemoved(input_context, device); + break; + + case InputEventType.KeyboardKey: + HandleKeyboard(input_context, device); + break; + } + Debug.WriteLine(type.ToString()); LibInput.DestroyEvent(pevent); } @@ -139,23 +212,91 @@ namespace OpenTK.Platform.Linux } } + void HandleDeviceAdded(IntPtr context, IntPtr device) + { + if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) + { + KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); + Keyboards.Add(keyboard.Id, keyboard); + } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) + { + Debug.Print("[Linux] Todo: libinput mouse device."); + } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Touch)) + { + Debug.Print("[Linux] Todo: libinput touch device."); + } + } + + void HandleDeviceRemoved(IntPtr context, IntPtr device) + { + if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) + { + int id = GetId(device); + Keyboards.Remove(id); + } + } + + void HandleKeyboard(IntPtr context, IntPtr device) + { + int id = GetId(device); + KeyboardDevice keyboard = Keyboards.FromHardwareId(id); + if (keyboard != null) + { + // Todo: update keyboard state + } + else + { + Debug.Print("[Linux] libinput ignoring invalid device id {0}", id); + } + } + + static int GetId(IntPtr device) + { + return LibInput.DeviceGetData(device).ToInt32(); + } + #endregion #region IKeyboardDriver2 implementation KeyboardState IKeyboardDriver2.GetState() { - return new KeyboardState(); + KeyboardState state = new KeyboardState(); + foreach (KeyboardDevice keyboard in Keyboards) + { + state.MergeBits(keyboard.State); + } + return state; } KeyboardState IKeyboardDriver2.GetState(int index) { - return new KeyboardState(); + KeyboardDevice device = Keyboards.FromIndex(index); + if (device != null) + { + return device.State; + } + else + { + return new KeyboardState(); + } } string IKeyboardDriver2.GetDeviceName(int index) { - return String.Empty; + KeyboardDevice device = Keyboards.FromIndex(index); + if (device != null) + { + return device.Name; + } + else + { + return String.Empty; + } } #endregion From 67727d2e9ba18ed48b6288c06ecd1eff4cc1ca64 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 15 Jul 2014 09:09:11 +0200 Subject: [PATCH 17/41] [Linux] Completed libinput keyboard driver implementation --- Source/OpenTK/OpenTK.csproj | 1 + Source/OpenTK/Platform/DeviceCollection.cs | 3 +- .../OpenTK/Platform/Linux/Bindings/Evdev.cs | 335 ++++++++++++++++++ .../Platform/Linux/Bindings/LibInput.cs | 39 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 104 +++--- .../OpenTK/Platform/Linux/LinuxKeyboardTTY.cs | 296 +--------------- 6 files changed, 437 insertions(+), 341 deletions(-) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Evdev.cs diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index b5f973ce..8a6f2608 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -821,6 +821,7 @@ + diff --git a/Source/OpenTK/Platform/DeviceCollection.cs b/Source/OpenTK/Platform/DeviceCollection.cs index 57306785..8229c291 100644 --- a/Source/OpenTK/Platform/DeviceCollection.cs +++ b/Source/OpenTK/Platform/DeviceCollection.cs @@ -31,7 +31,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -namespace OpenTK +namespace OpenTK.Platform { // Holds a collection of hardware devices with an associated id. // Note: 'id' refers to a unique hardware-specific device identifier. @@ -41,7 +41,6 @@ namespace OpenTK // that is added. class DeviceCollection : IEnumerable { - readonly object Sync = new object(); readonly Dictionary Map = new Dictionary(); readonly List Devices = new List(); diff --git a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs new file mode 100644 index 00000000..9b457c14 --- /dev/null +++ b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs @@ -0,0 +1,335 @@ +#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 OpenTK.Input; + +namespace OpenTK +{ + // 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, // Zzenkakuhankaku + 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 + } +} + diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index c557719d..f64353dc 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -43,7 +43,7 @@ namespace OpenTK.Platform.Linux class LibInput { - const string lib = "libinput"; + internal const string lib = "libinput"; [DllImport(lib, EntryPoint = "libinput_udev_create_for_seat", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr CreateContext(InputInterface @interface, @@ -95,6 +95,9 @@ namespace OpenTK.Platform.Linux [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_type", CallingConvention = CallingConvention.Cdecl)] public static extern InputEventType GetEventType(IntPtr @event); @@ -145,6 +148,12 @@ namespace OpenTK.Platform.Linux TouchFrame } + enum KeyState + { + Released = 0, + Pressed = 1 + } + [StructLayout(LayoutKind.Sequential)] class InputInterface { @@ -162,5 +171,33 @@ namespace OpenTK.Platform.Linux 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 Key { get { return GetKey(@event); } } + public uint KeyCount { get { return GetSeatKeyCount(@event); } } + public KeyState KeyState { get { return GetKeyState(@event); } } + public uint Time { get { return GetTime(@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_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); + + [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); + } } diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index d3853d82..917fb72c 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -90,6 +90,7 @@ namespace OpenTK.Platform.Linux public MouseState State; } + static readonly Key[] KeyMap = Evdev.KeyMap; DeviceCollection Keyboards = new DeviceCollection(); DeviceCollection Mice = new DeviceCollection(); @@ -105,7 +106,7 @@ namespace OpenTK.Platform.Linux { Debug.Print("[Linux] Initializing {0}", GetType().Name); - input_thread = new Thread(ProcessEvents); + input_thread = new Thread(InputThreadLoop); input_thread.IsBackground = true; // Todo: add static path fallback when udev is not installed. @@ -130,7 +131,9 @@ namespace OpenTK.Platform.Linux String.Format("[Input] LibInput.GetFD({0:x}) failed.", input_context)); } - input_thread.Start(); + ProcessEvents(input_context); + LibInput.Resume(input_context); + //input_thread.Start(); } #region Private Members @@ -152,55 +155,18 @@ namespace OpenTK.Platform.Linux return fd; } - void ProcessEvents() + void InputThreadLoop() { PollFD poll_fd = new PollFD(); poll_fd.fd = fd; poll_fd.events = PollFlags.In; - LibInput.Resume(input_context); - while (Interlocked.Read(ref exit) == 0) { int ret = Libc.poll(ref poll_fd, 1, -1); if (ret > 0 && (poll_fd.revents & PollFlags.In) != 0) { - // Data available - ret = LibInput.Dispatch(input_context); - if (ret != 0) - { - Debug.Print("[Input] LibInput.Dispatch({0:x}) failed. Error: {1}", - input_context, ret); - continue; - } - - IntPtr pevent = LibInput.GetEvent(input_context); - if (pevent == IntPtr.Zero) - { - Debug.Print("[Input] LibInput.GetEvent({0:x}) failed.", - input_context); - continue; - } - - IntPtr device = LibInput.GetDevice(pevent); - InputEventType type = LibInput.GetEventType(pevent); - switch (type) - { - case InputEventType.DeviceAdded: - HandleDeviceAdded(input_context, device); - break; - - case InputEventType.DeviceRemoved: - HandleDeviceRemoved(input_context, device); - break; - - case InputEventType.KeyboardKey: - HandleKeyboard(input_context, device); - break; - } - Debug.WriteLine(type.ToString()); - - LibInput.DestroyEvent(pevent); + ProcessEvents(input_context); } else if (ret < 0) { @@ -212,12 +178,55 @@ namespace OpenTK.Platform.Linux } } + 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); + Debug.Print(type.ToString()); + switch (type) + { + case InputEventType.DeviceAdded: + HandleDeviceAdded(input_context, device); + break; + + case InputEventType.DeviceRemoved: + HandleDeviceRemoved(input_context, device); + break; + + case InputEventType.KeyboardKey: + HandleKeyboard(input_context, device, LibInput.GetKeyboardEvent(pevent)); + break; + } + + LibInput.DestroyEvent(pevent); + } + } + void HandleDeviceAdded(IntPtr context, IntPtr device) { if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) { KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); Keyboards.Add(keyboard.Id, keyboard); + Debug.Print("[Linux] libinput: added keyboard device {0}", keyboard.Id); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) @@ -240,13 +249,20 @@ namespace OpenTK.Platform.Linux } } - void HandleKeyboard(IntPtr context, IntPtr device) + void HandleKeyboard(IntPtr context, IntPtr device, KeyboardEvent e) { int id = GetId(device); KeyboardDevice keyboard = Keyboards.FromHardwareId(id); if (keyboard != null) { - // Todo: update keyboard state + Key key = Key.Unknown; + uint raw = e.Key; + if (raw >= 0 && raw < KeyMap.Length) + { + key = KeyMap[raw]; + } + + keyboard.State.SetKeyState(key, e.KeyState == KeyState.Pressed); } else { @@ -265,6 +281,7 @@ namespace OpenTK.Platform.Linux KeyboardState IKeyboardDriver2.GetState() { + ProcessEvents(input_context); KeyboardState state = new KeyboardState(); foreach (KeyboardDevice keyboard in Keyboards) { @@ -275,6 +292,7 @@ namespace OpenTK.Platform.Linux KeyboardState IKeyboardDriver2.GetState(int index) { + ProcessEvents(input_context); KeyboardDevice device = Keyboards.FromIndex(index); if (device != null) { diff --git a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs index 63b80952..afe66bc1 100644 --- a/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs +++ b/Source/OpenTK/Platform/Linux/LinuxKeyboardTTY.cs @@ -199,301 +199,7 @@ namespace OpenTK.Platform.Linux return KeyMap[k]; } - #region KeyMap - - 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, // Zzenkakuhankaku - 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 + static readonly Key[] KeyMap = Evdev.KeyMap; #endregion From 468a8518cb9c2bf7b3f6dc2f80a35445e6dc2ac0 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 15 Jul 2014 17:28:31 +0200 Subject: [PATCH 18/41] [Linux] Fixed poll() in libinput event loop --- Source/OpenTK/Platform/Linux/Bindings/Poll.cs | 2 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 199 ++++++++++++------ 2 files changed, 140 insertions(+), 61 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs index a048398d..60a7bb8f 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs @@ -51,7 +51,7 @@ namespace OpenTK.Platform.Linux Out = 0x04, Error = 0x08, Hup = 0x10, - Nval = 0x20, + Invalid = 0x20, } [StructLayout(LayoutKind.Sequential)] diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index 917fb72c..28000a34 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -90,7 +90,9 @@ namespace OpenTK.Platform.Linux public MouseState State; } + static readonly object Sync = new object(); static readonly Key[] KeyMap = Evdev.KeyMap; + static long DeviceFDCount; DeviceCollection Keyboards = new DeviceCollection(); DeviceCollection Mice = new DeviceCollection(); @@ -105,35 +107,33 @@ namespace OpenTK.Platform.Linux public LinuxInput() { Debug.Print("[Linux] Initializing {0}", GetType().Name); - - input_thread = new Thread(InputThreadLoop); - input_thread.IsBackground = true; - - // Todo: add static path fallback when udev is not installed. - udev = Udev.New(); - if (udev == IntPtr.Zero) + Debug.Indent(); + try { - throw new NotSupportedException("[Input] Udev.New() failed."); - } + Semaphore ready = new Semaphore(0, 1); + input_thread = new Thread(InputThreadLoop); + input_thread.IsBackground = true; + input_thread.Start(ready); - input_context = LibInput.CreateContext(input_interface, - IntPtr.Zero, udev, "seat0"); - if (input_context == IntPtr.Zero) + // 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 { - throw new NotSupportedException( - String.Format("[Input] LibInput.CreateContext({0:x}) failed.", udev)); + Debug.Print("Initialization {0}", exit == 0 ? + "complete" : "failed"); + Debug.Unindent(); } - - fd = LibInput.GetFD(input_context); - if (fd < 0) - { - throw new NotSupportedException( - String.Format("[Input] LibInput.GetFD({0:x}) failed.", input_context)); - } - - ProcessEvents(input_context); - LibInput.Resume(input_context); - //input_thread.Start(); } #region Private Members @@ -142,7 +142,16 @@ namespace OpenTK.Platform.Linux static void CloseRestrictedHandler(int fd, IntPtr data) { Debug.Print("[Input] Closing fd {0}", fd); - Libc.close(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; @@ -152,30 +161,89 @@ namespace OpenTK.Platform.Linux 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() + 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); - if (ret > 0 && (poll_fd.revents & PollFlags.In) != 0) + if (ret > 0 && (poll_fd.revents & (PollFlags.In | PollFlags.Pri)) != 0) { ProcessEvents(input_context); } - else if (ret < 0) + + if ((poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0) { // An error has occurred - Debug.Print("[Input] Exiting input thread {0} [ret:{1} events:{2}]", + Debug.Print("[Input] Exiting input thread {0} due to error [ret:{1} events:{2}]", input_thread.ManagedThreadId, ret, poll_fd.revents); Interlocked.Increment(ref exit); } } + Debug.Print("[Input] Exited input loop.", poll_fd.fd, poll_fd.events); + } + + 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) @@ -201,19 +269,23 @@ namespace OpenTK.Platform.Linux IntPtr device = LibInput.GetDevice(pevent); InputEventType type = LibInput.GetEventType(pevent); Debug.Print(type.ToString()); - switch (type) + + lock (Sync) { - case InputEventType.DeviceAdded: - HandleDeviceAdded(input_context, device); - break; + switch (type) + { + case InputEventType.DeviceAdded: + HandleDeviceAdded(input_context, device); + break; - case InputEventType.DeviceRemoved: - HandleDeviceRemoved(input_context, device); - break; + case InputEventType.DeviceRemoved: + HandleDeviceRemoved(input_context, device); + break; - case InputEventType.KeyboardKey: - HandleKeyboard(input_context, device, LibInput.GetKeyboardEvent(pevent)); - break; + case InputEventType.KeyboardKey: + HandleKeyboard(input_context, device, LibInput.GetKeyboardEvent(pevent)); + break; + } } LibInput.DestroyEvent(pevent); @@ -281,39 +353,46 @@ namespace OpenTK.Platform.Linux KeyboardState IKeyboardDriver2.GetState() { - ProcessEvents(input_context); - KeyboardState state = new KeyboardState(); - foreach (KeyboardDevice keyboard in Keyboards) + lock (Sync) { - state.MergeBits(keyboard.State); + KeyboardState state = new KeyboardState(); + foreach (KeyboardDevice keyboard in Keyboards) + { + state.MergeBits(keyboard.State); + } + return state; } - return state; } KeyboardState IKeyboardDriver2.GetState(int index) { - ProcessEvents(input_context); - KeyboardDevice device = Keyboards.FromIndex(index); - if (device != null) + lock (Sync) { - return device.State; - } - else - { - return new KeyboardState(); + KeyboardDevice device = Keyboards.FromIndex(index); + if (device != null) + { + return device.State; + } + else + { + return new KeyboardState(); + } } } string IKeyboardDriver2.GetDeviceName(int index) { - KeyboardDevice device = Keyboards.FromIndex(index); - if (device != null) + lock (Sync) { - return device.Name; - } - else - { - return String.Empty; + KeyboardDevice device = Keyboards.FromIndex(index); + if (device != null) + { + return device.Name; + } + else + { + return String.Empty; + } } } From 4406d2db0d1c8f75f57459dbbea1cd97f23029d6 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 15 Jul 2014 22:54:04 +0200 Subject: [PATCH 19/41] [Linux] Fixed keyboard polling --- Source/OpenTK/Platform/Linux/Bindings/Evdev.cs | 2 +- Source/OpenTK/Platform/Linux/Bindings/Poll.cs | 2 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 13 ++++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs index 9b457c14..f60834f4 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs @@ -135,7 +135,7 @@ namespace OpenTK Key.Keypad0, Key.KeypadPeriod, Key.Unknown, - Key.Unknown, // Zzenkakuhankaku + Key.Unknown, // zenkakuhankaku Key.Unknown, // 102ND Key.F11, // 88-95 diff --git a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs index 60a7bb8f..f78d6d73 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Poll.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Poll.cs @@ -34,7 +34,7 @@ namespace OpenTK.Platform.Linux { partial class Libc { - [DllImport(lib, CallingConvention = CallingConvention.Cdecl)] + [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) diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index 28000a34..b3b54d22 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -193,11 +193,10 @@ namespace OpenTK.Platform.Linux ProcessEvents(input_context); } - if ((poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0) + if (ret < 0 || (poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0) { - // An error has occurred - Debug.Print("[Input] Exiting input thread {0} due to error [ret:{1} events:{2}]", - input_thread.ManagedThreadId, ret, poll_fd.revents); + Debug.Print("[Input] Exiting input loop {0} due to poll error [ret:{1} events:{2}]. Error: {3}.", + input_thread.ManagedThreadId, ret, poll_fd.revents, Marshal.GetLastWin32Error()); Interlocked.Increment(ref exit); } } @@ -268,7 +267,6 @@ namespace OpenTK.Platform.Linux IntPtr device = LibInput.GetDevice(pevent); InputEventType type = LibInput.GetEventType(pevent); - Debug.Print(type.ToString()); lock (Sync) { @@ -334,6 +332,11 @@ namespace OpenTK.Platform.Linux key = KeyMap[raw]; } + if (key == Key.Unknown) + { + Debug.Print("[Linux] Unknown key with code '{0}'", raw); + } + keyboard.State.SetKeyState(key, e.KeyState == KeyState.Pressed); } else From c81833a201fd332a6c0654e9c5ef40e1c95bbeb9 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Tue, 15 Jul 2014 23:32:57 +0200 Subject: [PATCH 20/41] [Examples] Reverted mistaken change --- Source/Examples/Main.cs | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/Source/Examples/Main.cs b/Source/Examples/Main.cs index c23e99b5..84dba6d7 100644 --- a/Source/Examples/Main.cs +++ b/Source/Examples/Main.cs @@ -101,32 +101,6 @@ namespace Examples public static void Main(string[] args) { Trace.Listeners.Add(new ConsoleTraceListener()); - using (Toolkit.Init()) - { - while (true) - { - var state = OpenTK.Input.Keyboard.GetState(); - if (!state.IsConnected) - { - break; - } - else if (state.IsKeyDown(OpenTK.Input.Key.Escape)) - { - break; - } - } - } - return; - - Tests.GameWindowStates.Main(); - return; - - using (var gw = new GameWindow()) - { - gw.KeyDown += (sender, e) => gw.Exit(); - gw.Run(60); - } - return; if (args.Length > 0) { From 340d34b07b5fce894222e24f44af5b3cf452efe5 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 11:23:23 +0200 Subject: [PATCH 21/41] [Input] MouseState position is now stored in floating point Several platforms provide subpixel accuracy for mouse position. OpenTK can now take advantage of that. --- Source/OpenTK/Input/MouseState.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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); From bef4901659dfdb71449008c7da1ac7d662f1d9c7 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 11:24:53 +0200 Subject: [PATCH 22/41] [Linux] Implemented libinput IMouseDriver2 --- .../OpenTK/Platform/Linux/Bindings/Evdev.cs | 119 ++++++++++- .../Platform/Linux/Bindings/LibInput.cs | 123 +++++++++++- Source/OpenTK/Platform/Linux/Bindings/Libc.cs | 7 + Source/OpenTK/Platform/Linux/LinuxFactory.cs | 13 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 187 +++++++++++++++--- 5 files changed, 409 insertions(+), 40 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs index f60834f4..8674db33 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs @@ -30,7 +30,7 @@ using System; using OpenTK.Input; -namespace OpenTK +namespace OpenTK.Platform.Linux { // Bindings for linux/input.h class Evdev @@ -330,6 +330,123 @@ namespace OpenTK }; #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; + case EvdevButton.BTN9: + return MouseButton.LastButton; + default: + return MouseButton.LastButton; + } + } + } + + 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/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index f64353dc..29864b93 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -98,6 +98,9 @@ namespace OpenTK.Platform.Linux [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); @@ -148,17 +151,54 @@ namespace OpenTK.Platform.Linux 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 { - IntPtr open; - IntPtr close; + internal readonly IntPtr open; + internal readonly IntPtr close; public InputInterface( OpenRestrictedCallback open_restricted, @@ -179,25 +219,90 @@ namespace OpenTK.Platform.Linux 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); } } - public uint Time { get { return GetTime(@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_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); - [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); } } + // Are the following useful? + //public Fixed24 TransformedX(int width) { return GetAbsXTransformed(@event, width); } + //public Fixed24 TransformedY(int height) { return GetAbsXTransformed(@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 index 06673659..db026a77 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Libc.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Libc.cs @@ -97,6 +97,13 @@ namespace OpenTK.Platform.Linux public static extern bool isatty(int fd); } + enum ErrorNumber + { + Interrupted = 4, + Again = 11, + InvalidValue = 22, + } + [Flags] enum OpenFlags { diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 943e9dcd..23a3b2cb 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -47,8 +47,7 @@ namespace OpenTK.Platform.Linux IntPtr egl_display; IJoystickDriver2 JoystickDriver; - IKeyboardDriver2 KeyboardDriver; - IMouseDriver2 MouseDriver; + LinuxInput MouseKeyboardDriver; const string gpu_path = "/dev/dri"; // card0, card1, ... @@ -217,14 +216,18 @@ namespace OpenTK.Platform.Linux { lock (this) { - KeyboardDriver = KeyboardDriver ?? new LinuxInput(); - return KeyboardDriver; + MouseKeyboardDriver = MouseKeyboardDriver ?? new LinuxInput(); + return MouseKeyboardDriver; } } public override IMouseDriver2 CreateMouseDriver() { - throw new NotImplementedException(); + lock (this) + { + MouseKeyboardDriver = MouseKeyboardDriver ?? new LinuxInput(); + return MouseKeyboardDriver; + } } public override IJoystickDriver2 CreateJoystickDriver() diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index b3b54d22..ddd3a8ae 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -37,17 +37,16 @@ namespace OpenTK.Platform.Linux { class LinuxInput : IKeyboardDriver2, IMouseDriver2, IDisposable { - class KeyboardDevice + class DeviceBase { readonly IntPtr Device; string name; string output; - public KeyboardDevice(IntPtr device, int id) + public DeviceBase(IntPtr device, int id) { Device = device; Id = id; - State.SetIsConnected(true); } public int Id @@ -79,15 +78,28 @@ namespace OpenTK.Platform.Linux return output; } } - - public KeyboardState State; } - class MouseDevice + class KeyboardDevice : DeviceBase + { + public KeyboardState State; + + public KeyboardDevice(IntPtr device, int id) + : base(device, id) + { + State.SetIsConnected(true); + } + } + + class MouseDevice : DeviceBase { - public int FD; - public string Name; public MouseState State; + + public MouseDevice(IntPtr device, int id) + : base(device, id) + { + State.SetIsConnected(true); + } } static readonly object Sync = new object(); @@ -96,6 +108,11 @@ namespace OpenTK.Platform.Linux DeviceCollection Keyboards = new DeviceCollection(); DeviceCollection Mice = new DeviceCollection(); + // 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( @@ -188,15 +205,20 @@ namespace OpenTK.Platform.Linux 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; + if (ret > 0 && (poll_fd.revents & (PollFlags.In | PollFlags.Pri)) != 0) { ProcessEvents(input_context); } - if (ret < 0 || (poll_fd.revents & (PollFlags.Hup | PollFlags.Error | PollFlags.Invalid)) != 0) + 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, Marshal.GetLastWin32Error()); + input_thread.ManagedThreadId, ret, poll_fd.revents, error); Interlocked.Increment(ref exit); } } @@ -281,7 +303,23 @@ namespace OpenTK.Platform.Linux break; case InputEventType.KeyboardKey: - HandleKeyboard(input_context, device, LibInput.GetKeyboardEvent(pevent)); + 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; } } @@ -296,17 +334,19 @@ namespace OpenTK.Platform.Linux { KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); Keyboards.Add(keyboard.Id, keyboard); - Debug.Print("[Linux] libinput: added keyboard device {0}", keyboard.Id); + Debug.Print("[Input] Added keyboard device {0}", keyboard.Id); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) { - Debug.Print("[Linux] Todo: libinput mouse device."); + MouseDevice mouse = new MouseDevice(device, Mice.Count); + Mice.Add(mouse.Id, mouse); + Debug.Print("[Input] Added mouse device {0}", mouse.Id); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Touch)) { - Debug.Print("[Linux] Todo: libinput touch device."); + Debug.Print("[Input] Todo: touch device."); } } @@ -317,13 +357,17 @@ namespace OpenTK.Platform.Linux int id = GetId(device); Keyboards.Remove(id); } + + if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) + { + int id = GetId(device); + Mice.Remove(id); + } } - void HandleKeyboard(IntPtr context, IntPtr device, KeyboardEvent e) + void HandleKeyboard(KeyboardDevice device, KeyboardEvent e) { - int id = GetId(device); - KeyboardDevice keyboard = Keyboards.FromHardwareId(id); - if (keyboard != null) + if (device != null) { Key key = Key.Unknown; uint raw = e.Key; @@ -337,19 +381,89 @@ namespace OpenTK.Platform.Linux Debug.Print("[Linux] Unknown key with code '{0}'", raw); } - keyboard.State.SetKeyState(key, e.KeyState == KeyState.Pressed); + device.State.SetKeyState(key, e.KeyState == KeyState.Pressed); } - else + } + + void HandlePointerAxis(MouseDevice mouse, PointerEvent e) + { + if (mouse != null) { - Debug.Print("[Linux] libinput ignoring invalid device id {0}", id); + 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) + { + 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.Position += delta; + } + } + + void HandlePointerMotionAbsolute(MouseDevice mouse, PointerEvent e) + { + Vector2 position = new Vector2(e.X, e.Y); + if (mouse != null) + { + mouse.State.Position = position; + } + CursorPosition = position; // update global cursor position + } + static int GetId(IntPtr device) { return LibInput.DeviceGetData(device).ToInt32(); } + KeyboardDevice GetKeyboard(IntPtr device) + { + int id = GetId(device); + KeyboardDevice keyboard = Keyboards.FromHardwareId(id); + if (keyboard == null) + { + Debug.Print("[Input] Keyboard {0} does not exist in device list.", id); + } + return keyboard; + } + + MouseDevice GetMouse(IntPtr device) + { + int id = GetId(device); + MouseDevice mouse = Mice.FromHardwareId(id); + if (mouse == null) + { + Debug.Print("[Input] Mouse {0} does not exist in device list.", id); + } + return mouse; + } + #endregion #region IKeyboardDriver2 implementation @@ -405,22 +519,45 @@ namespace OpenTK.Platform.Linux MouseState IMouseDriver2.GetState() { - throw new NotImplementedException(); + lock (Sync) + { + MouseState state = new MouseState(); + foreach (MouseDevice mouse in Mice) + { + state.MergeBits(mouse.State); + } + return state; + } } MouseState IMouseDriver2.GetState(int index) { - throw new NotImplementedException(); + lock (Sync) + { + MouseDevice device = Mice.FromIndex(index); + if (device != null) + { + return device.State; + } + else + { + return new MouseState(); + } + } } void IMouseDriver2.SetPosition(double x, double y) { - throw new NotImplementedException(); + // 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, (float)y); } MouseState IMouseDriver2.GetCursorState() { - throw new NotImplementedException(); + MouseState state = (this as IMouseDriver2).GetState(); + state.Position = CursorPosition + CursorOffset; + return state; } #endregion From e61b39a1a1b354ffbc461607e6db9eec14de7199 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 11:34:01 +0200 Subject: [PATCH 23/41] [Linux] Fixed GameWindow.ClientSize values. --- .../Platform/Linux/LinuxNativeWindow.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index eba4214c..d0e32f77 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -55,11 +55,23 @@ namespace OpenTK.Platform.Linux Debug.Print("[KMS] Creating window on display {0:x}", display); Title = title; - bounds = new Rectangle(0, 0, width, height); - client_size = bounds.Size; + + 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, 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); @@ -74,15 +86,6 @@ namespace OpenTK.Platform.Linux format = SurfaceFormat.XRGB8888; } - // 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. - if (display_device != null) - { - width = display_device.Width; - height = display_device.Height; - } - 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, From 3881992bf7ad7ce96250dbd9a40621bfe0f9e328 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 12:18:24 +0200 Subject: [PATCH 24/41] [Linux] Implemented INativeWindow keyboard/mouse events The mouse cursor is now confined to the display bounds. --- .../OpenTK/Platform/Linux/Bindings/Evdev.cs | 6 +- .../Platform/Linux/Bindings/LibInput.cs | 5 +- Source/OpenTK/Platform/Linux/LinuxInput.cs | 36 +++++++- .../Platform/Linux/LinuxNativeWindow.cs | 82 ++++++++++++++++++- 4 files changed, 118 insertions(+), 11 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs index 8674db33..11988ebe 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Evdev.cs @@ -28,6 +28,7 @@ #endregion using System; +using System.Diagnostics; using OpenTK.Input; namespace OpenTK.Platform.Linux @@ -359,10 +360,9 @@ namespace OpenTK.Platform.Linux return MouseButton.Button8; case EvdevButton.BTN8: return MouseButton.Button9; - case EvdevButton.BTN9: - return MouseButton.LastButton; default: - return MouseButton.LastButton; + Debug.Print("[Input] Unknown EvdevButton {0}", button); + return MouseButton.Left; } } } diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index 29864b93..15aa00db 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -258,9 +258,8 @@ namespace OpenTK.Platform.Linux public Fixed24 DeltaY { get { return GetDY(@event); } } public Fixed24 X { get { return GetAbsX(@event); } } public Fixed24 Y { get { return GetAbsY(@event); } } - // Are the following useful? - //public Fixed24 TransformedX(int width) { return GetAbsXTransformed(@event, width); } - //public Fixed24 TransformedY(int height) { return GetAbsXTransformed(@event, height); } + 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); diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index ddd3a8ae..390f0228 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -28,7 +28,9 @@ #endregion using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Drawing; using System.Runtime.InteropServices; using System.Threading; using OpenTK.Input; @@ -108,6 +110,9 @@ namespace OpenTK.Platform.Linux 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) @@ -210,6 +215,12 @@ namespace OpenTK.Platform.Linux 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); @@ -225,6 +236,19 @@ namespace OpenTK.Platform.Linux 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 Setup() { // Todo: add static path fallback when udev is not installed. @@ -425,16 +449,22 @@ namespace OpenTK.Platform.Linux { mouse.State.Position += delta; } + + CursorPosition = new Vector2( + MathHelper.Clamp(CursorPosition.X + delta.X, bounds.Left, bounds.Right), + MathHelper.Clamp(CursorPosition.Y + delta.Y, bounds.Top, bounds.Bottom)); } void HandlePointerMotionAbsolute(MouseDevice mouse, PointerEvent e) { - Vector2 position = new Vector2(e.X, e.Y); if (mouse != null) { - mouse.State.Position = position; + mouse.State.Position = new Vector2(e.X, e.Y); } - CursorPosition = position; // update global cursor position + + CursorPosition = new Vector2( + e.TransformedX(bounds.Width), + e.TransformedY(bounds.Height)); } static int GetId(IntPtr device) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index d0e32f77..ee6edf86 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -32,6 +32,7 @@ using System.Diagnostics; using System.Drawing; using System.Runtime.InteropServices; using OpenTK.Graphics; +using OpenTK.Input; using OpenTK.Platform.Egl; namespace OpenTK.Platform.Linux @@ -43,9 +44,13 @@ namespace OpenTK.Platform.Linux LinuxWindowInfo window; string title; Icon icon; - bool exists; Rectangle bounds; Size client_size; + bool exists; + bool is_focused; + + KeyboardState previous_keyboard; + MouseState previous_mouse; public LinuxNativeWindow(IntPtr display, IntPtr gbm, int fd, int x, int y, int width, int height, string title, @@ -147,10 +152,83 @@ namespace OpenTK.Platform.Linux 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) + { + for (MouseButton i = 0; i < MouseButton.LastButton; i++) + { + if (mouse[i] && !previous_mouse[i]) + { + OnMouseDown(i); + } + + if (!mouse[i] && previous_mouse[i]) + { + OnMouseUp(i); + } + + if (mouse.Position != previous_mouse.Position) + { + OnMouseMove(mouse.X, mouse.Y); + } + + if (mouse.Scroll != previous_mouse.Scroll) + { + OnMouseWheel(mouse.Scroll.X, mouse.Scroll.Y); + } + + // 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); + } + } + #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(); } @@ -216,7 +294,7 @@ namespace OpenTK.Platform.Linux { get { - return true; + return is_focused; } } From 4c6bb7a38e7d7c1f85640da928ae3d22a25faeb8 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 14:25:55 +0200 Subject: [PATCH 25/41] [Linux] Corrected values for mouse move and scroll events --- .../Platform/Linux/LinuxNativeWindow.cs | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index ee6edf86..d27557d5 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -183,29 +183,31 @@ namespace OpenTK.Platform.Linux { OnMouseUp(i); } + } - if (mouse.Position != previous_mouse.Position) - { - OnMouseMove(mouse.X, mouse.Y); - } + if (mouse.X != previous_mouse.X || mouse.Y != previous_mouse.Y) + { + OnMouseMove(mouse.X, mouse.Y); + } - if (mouse.Scroll != previous_mouse.Scroll) - { - OnMouseWheel(mouse.Scroll.X, mouse.Scroll.Y); - } + 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); + } - // 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); - } + // 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; From 015acba313e82b9ab36134e970dd8e0e7d9936cc Mon Sep 17 00:00:00 2001 From: thefiddler Date: Wed, 16 Jul 2014 14:51:14 +0200 Subject: [PATCH 26/41] [KMS] Implemented INativeWindow.PointToClient/Screen --- .../Platform/Linux/LinuxNativeWindow.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index d27557d5..c1f9cdd3 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -241,14 +241,27 @@ namespace OpenTK.Platform.Linux public override Point PointToClient(Point point) { - // Todo - return 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) { - // Todo - return 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) From 153522c0a4aa15d864a37bad117766367c4f7853 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 17 Jul 2014 09:25:16 +0200 Subject: [PATCH 27/41] [Linux] Implemented MouseCursor --- Source/OpenTK/DisplayDevice.cs | 20 +++ Source/OpenTK/OpenTK.csproj | 2 + Source/OpenTK/Platform/Linux/Bindings/Drm.cs | 7 ++ Source/OpenTK/Platform/Linux/Bindings/Gbm.cs | 116 ++++++++++++++++-- Source/OpenTK/Platform/Linux/Bindings/Kms.cs | 46 +++++++ Source/OpenTK/Platform/Linux/DefaultCursor.cs | 76 ++++++++++++ .../Platform/Linux/LinuxDisplayDriver.cs | 6 +- .../Platform/Linux/LinuxGraphicsContext.cs | 22 ++-- Source/OpenTK/Platform/Linux/LinuxInput.cs | 21 +++- .../Platform/Linux/LinuxNativeWindow.cs | 96 ++++++++++++++- .../OpenTK/Platform/Linux/LinuxWindowInfo.cs | 3 +- 11 files changed, 387 insertions(+), 28 deletions(-) create mode 100644 Source/OpenTK/Platform/Linux/Bindings/Kms.cs create mode 100644 Source/OpenTK/Platform/Linux/DefaultCursor.cs 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/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj index 8a6f2608..61e11bc4 100644 --- a/Source/OpenTK/OpenTK.csproj +++ b/Source/OpenTK/OpenTK.csproj @@ -822,6 +822,8 @@ + + diff --git a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs index c0704837..583dd449 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Drm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Drm.cs @@ -89,6 +89,13 @@ namespace OpenTK.Platform.Linux [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 diff --git a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs index efde6e8b..d298d5ea 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/Gbm.cs @@ -32,9 +32,8 @@ using System.Runtime.InteropServices; namespace OpenTK.Platform.Linux { - using Device = IntPtr; // opaque pointer "struct gbm_device*" + using Device = IntPtr; // struct gbm_device* using Surface = IntPtr; - using BufferObject = IntPtr; using BufferObjectHandle = IntPtr; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -44,23 +43,32 @@ namespace OpenTK.Platform.Linux { 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(BufferObject bo); + public static extern Device BOGetDevice(IntPtr bo); [DllImport(lib, EntryPoint = "gbm_bo_get_handle", CallingConvention = CallingConvention.Cdecl)] - public static extern BufferObjectHandle BOGetHandle(BufferObject bo); + public static extern BufferObjectHandle BOGetHandle(IntPtr bo); [DllImport(lib, EntryPoint = "gbm_bo_get_height", CallingConvention = CallingConvention.Cdecl)] - public static extern int BOGetHeight(BufferObject bo); + public static extern int BOGetHeight(IntPtr bo); [DllImport(lib, EntryPoint = "gbm_bo_get_width", CallingConvention = CallingConvention.Cdecl)] - public static extern int BOGetWidth(BufferObject bo); + public static extern int BOGetWidth(IntPtr bo); [DllImport(lib, EntryPoint = "gbm_bo_get_stride", CallingConvention = CallingConvention.Cdecl)] - public static extern int BOGetStride(BufferObject bo); + public static extern int BOGetStride(IntPtr bo); [DllImport(lib, EntryPoint = "gbm_bo_set_user_data", CallingConvention = CallingConvention.Cdecl)] - public static extern void BOSetUserData(BufferObject bo, IntPtr data, DestroyUserDataCallback callback); + 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); @@ -174,5 +182,97 @@ namespace OpenTK.Platform.Linux 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/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 index ad38c523..928eac2c 100644 --- a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -38,6 +38,7 @@ 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; @@ -77,8 +78,9 @@ namespace OpenTK.Platform.Linux } } - public LinuxDisplay(IntPtr c, IntPtr e, IntPtr r) + public LinuxDisplay(int fd, IntPtr c, IntPtr e, IntPtr r) { + FD = fd; Connector = c; Encoder = e; Crtc = r; @@ -327,7 +329,7 @@ namespace OpenTK.Platform.Linux if (crtc == null) return false; - display = new LinuxDisplay((IntPtr)c, (IntPtr)encoder, (IntPtr)crtc); + display = new LinuxDisplay(fd, (IntPtr)c, (IntPtr)encoder, (IntPtr)crtc); return true; } diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs index eaa6be42..63122f71 100644 --- a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -46,7 +46,7 @@ namespace OpenTK.Platform.Linux /// class LinuxGraphicsContext : Egl.EglUnixContext { - IntPtr bo, bo_next; + BufferObject bo, bo_next; int fd; bool is_flip_queued; int swap_interval; @@ -192,29 +192,29 @@ namespace OpenTK.Platform.Linux } } - IntPtr LockSurface() + BufferObject LockSurface() { IntPtr gbm_surface = WindowInfo.Handle; return Gbm.LockFrontBuffer(gbm_surface); } - int GetFramebuffer(IntPtr bo) + int GetFramebuffer(BufferObject bo) { - if (bo == IntPtr.Zero) + if (bo == BufferObject.Zero) goto fail; - int bo_handle = Gbm.BOGetHandle(bo).ToInt32(); + int bo_handle = bo.Handle; if (bo_handle == 0) { Debug.Print("[KMS] Gbm.BOGetHandle({0:x}) failed.", bo); goto fail; } - int width = Gbm.BOGetWidth(bo); - int height = Gbm.BOGetHeight(bo); + int width = bo.Width; + int height = bo.Height; int bpp = Mode.ColorFormat.BitsPerPixel; int depth = Mode.Depth; - int stride = Gbm.BOGetStride(bo); + int stride = bo.Stride; if (width == 0 || height == 0 || bpp == 0) { @@ -235,7 +235,7 @@ namespace OpenTK.Platform.Linux goto fail; } - Gbm.BOSetUserData(bo, (IntPtr)buffer, DestroyFB); + bo.SetUserData((IntPtr)buffer, DestroyFB); return buffer; fail: @@ -255,9 +255,9 @@ namespace OpenTK.Platform.Linux } static readonly DestroyUserDataCallback DestroyFB = HandleDestroyFB; - static void HandleDestroyFB(IntPtr bo, IntPtr data) + static void HandleDestroyFB(BufferObject bo, IntPtr data) { - IntPtr gbm = Gbm.BOGetDevice(bo); + IntPtr gbm = bo.Device; int fb = data.ToInt32(); Debug.Print("[KMS] Destroying framebuffer {0}", fb); diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index 390f0228..6f4185fa 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -249,6 +249,20 @@ namespace OpenTK.Platform.Linux } } + 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); + 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. @@ -453,6 +467,7 @@ namespace OpenTK.Platform.Linux CursorPosition = new Vector2( MathHelper.Clamp(CursorPosition.X + delta.X, bounds.Left, bounds.Right), MathHelper.Clamp(CursorPosition.Y + delta.Y, bounds.Top, bounds.Bottom)); + UpdateCursor(); } void HandlePointerMotionAbsolute(MouseDevice mouse, PointerEvent e) @@ -465,6 +480,7 @@ namespace OpenTK.Platform.Linux CursorPosition = new Vector2( e.TransformedX(bounds.Width), e.TransformedY(bounds.Height)); + UpdateCursor(); } static int GetId(IntPtr device) @@ -580,7 +596,10 @@ namespace OpenTK.Platform.Linux { // 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, (float)y); + CursorOffset = new Vector2( + (float)x - CursorPosition.X, + (float)y - CursorPosition.Y); + UpdateCursor(); } MouseState IMouseDriver2.GetCursorState() diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index c1f9cdd3..a0549d98 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -48,10 +48,17 @@ namespace OpenTK.Platform.Linux 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; + + 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, @@ -67,7 +74,7 @@ namespace OpenTK.Platform.Linux throw new NotSupportedException("[KMS] Driver does not currently support headless systems"); } - window = new LinuxWindowInfo(display, fd, display_device.Id as LinuxDisplay); + 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 @@ -106,11 +113,74 @@ namespace OpenTK.Platform.Linux window.CreateWindowSurface(mode.Index.Value); Debug.Print("[KMS] Created EGL surface {0:x}", window.Surface); - // Todo: create mouse cursor + cursor_default = CreateCursor(gbm, Cursors.Default); + Cursor = MouseCursor.Default; exists = true; } - SurfaceFormat GetSurfaceFormat(IntPtr display, GraphicsMode mode) + #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; + BufferObject bo = Gbm.CreateBuffer( + gbm, width, height, format, usage); + + if (bo == BufferObject.Zero) + { + Debug.Print("[KMS] Gbm.CreateBuffer({0:X}, {1}, {2}, {3}, {4}) failed.", + gbm, width, height, format, usage); + 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) + { + // nothing to do + } + else + { + cursor_custom = CreateCursor(window.BufferManager, cursor); + } + + 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 @@ -222,6 +292,8 @@ namespace OpenTK.Platform.Linux } } + #endregion + #region INativeWindow Members public override void ProcessEvents() @@ -388,10 +460,13 @@ namespace OpenTK.Platform.Linux { get { - return false; + return is_cursor_visible; } set { + if (value) + { + } } } @@ -399,10 +474,21 @@ namespace OpenTK.Platform.Linux { get { - return MouseCursor.Empty; + return cursor_current; } set { + if (cursor_current != value) + { + if (cursor_custom != BufferObject.Zero) + { + cursor_custom.Dispose(); + } + + SetCursor(value); + + cursor_current = value; + } } } diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs index b6df115a..5a5af099 100644 --- a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -37,8 +37,9 @@ namespace OpenTK.Platform.Linux { public int FD { get; private set; } public LinuxDisplay DisplayDevice { get; private set; } + public IntPtr BufferManager { get; private set; } - public LinuxWindowInfo(IntPtr display, int fd, LinuxDisplay display_device) + public LinuxWindowInfo(IntPtr display, int fd, IntPtr gbm, LinuxDisplay display_device) : base(IntPtr.Zero, display, IntPtr.Zero) { if (display_device == null) From fd6ff962a1f986c9a5b445cc541c297da4714168 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 17 Jul 2014 09:45:13 +0200 Subject: [PATCH 28/41] [Linux] Implemented CursorVisible --- .../Platform/Linux/LinuxNativeWindow.cs | 42 +++++++++++++++---- .../OpenTK/Platform/Linux/LinuxWindowInfo.cs | 1 + 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index a0549d98..866001df 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -56,6 +56,7 @@ namespace OpenTK.Platform.Linux MouseCursor cursor_current; BufferObject cursor_custom; BufferObject cursor_default; + BufferObject cursor_empty; IntPtr gbm_surface; @@ -114,6 +115,7 @@ namespace OpenTK.Platform.Linux 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; } @@ -133,13 +135,16 @@ namespace OpenTK.Platform.Linux 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] Gbm.CreateBuffer({0:X}, {1}, {2}, {3}, {4}) failed.", - gbm, width, height, format, usage); + Debug.Print("[KMS] Failed to create buffer."); return bo; } @@ -169,15 +174,28 @@ namespace OpenTK.Platform.Linux } else if (cursor == MouseCursor.Empty) { - // nothing to do + bo = cursor_empty; } else { + if (cursor_custom != BufferObject.Zero) + cursor_custom.Dispose(); cursor_custom = CreateCursor(window.BufferManager, cursor); + bo = cursor_custom; } - Drm.SetCursor(window.FD, window.DisplayDevice.Id, - bo.Handle, bo.Width, bo.Height, cursor.X, cursor.Y); + // 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) @@ -464,9 +482,15 @@ namespace OpenTK.Platform.Linux } set { - if (value) + if (value && !is_cursor_visible) { + SetCursor(cursor_current); } + else if (!value && is_cursor_visible) + { + SetCursor(MouseCursor.Empty); + } + is_cursor_visible = value; } } @@ -485,8 +509,10 @@ namespace OpenTK.Platform.Linux cursor_custom.Dispose(); } - SetCursor(value); - + if (CursorVisible) + { + SetCursor(value); + } cursor_current = value; } } diff --git a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs index 5a5af099..e1d91e9a 100644 --- a/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs +++ b/Source/OpenTK/Platform/Linux/LinuxWindowInfo.cs @@ -46,6 +46,7 @@ namespace OpenTK.Platform.Linux 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. From 36bb36663849ab1bb77564a4d95173da4c02f6e9 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 17 Jul 2014 11:20:01 +0200 Subject: [PATCH 29/41] [Linux] Improved mouse cursor behavior --- Source/OpenTK/Platform/Linux/LinuxInput.cs | 6 ++--- .../Platform/Linux/LinuxNativeWindow.cs | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index 6f4185fa..eca19e6d 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -255,7 +255,7 @@ namespace OpenTK.Platform.Linux (int)Math.Round(CursorPosition.X + CursorOffset.X), (int)Math.Round(CursorPosition.Y + CursorOffset.Y)); - DisplayDevice display = DisplayDevice.FromPoint(p.X, p.Y); + DisplayDevice display = DisplayDevice.FromPoint(p.X, p.Y) ?? DisplayDevice.Default; if (display != null) { LinuxDisplay d = (LinuxDisplay)display.Id; @@ -465,8 +465,8 @@ namespace OpenTK.Platform.Linux } CursorPosition = new Vector2( - MathHelper.Clamp(CursorPosition.X + delta.X, bounds.Left, bounds.Right), - MathHelper.Clamp(CursorPosition.Y + delta.Y, bounds.Top, bounds.Bottom)); + MathHelper.Clamp(CursorPosition.X + delta.X, bounds.Left, bounds.Right - 1), + MathHelper.Clamp(CursorPosition.Y + delta.Y, bounds.Top, bounds.Bottom - 1)); UpdateCursor(); } diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index 866001df..8391f53e 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -260,6 +260,7 @@ namespace OpenTK.Platform.Linux MouseState ProcessMouse(MouseState mouse) { + // Handle mouse buttons for (MouseButton i = 0; i < MouseButton.LastButton; i++) { if (mouse[i] && !previous_mouse[i]) @@ -273,11 +274,29 @@ namespace OpenTK.Platform.Linux } } - if (mouse.X != previous_mouse.X || mouse.Y != previous_mouse.Y) + // Handle mouse movement { - OnMouseMove(mouse.X, mouse.Y); + 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.X, 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; @@ -285,6 +304,7 @@ namespace OpenTK.Platform.Linux 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) From 4f037c882abfdd00a92062de8fcd7b25698b1425 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 17 Jul 2014 11:59:04 +0200 Subject: [PATCH 30/41] [KMS] Print device names when adding input devices --- Source/OpenTK/Platform/Linux/LinuxInput.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index eca19e6d..a79ec37c 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -372,14 +372,14 @@ namespace OpenTK.Platform.Linux { KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); Keyboards.Add(keyboard.Id, keyboard); - Debug.Print("[Input] Added keyboard device {0}", keyboard.Id); + Debug.Print("[Input] Added keyboard device {0} '{1}'", keyboard.Id, keyboard.Name); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) { MouseDevice mouse = new MouseDevice(device, Mice.Count); Mice.Add(mouse.Id, mouse); - Debug.Print("[Input] Added mouse device {0}", mouse.Id); + Debug.Print("[Input] Added mouse device {0} '{1}'", mouse.Id, mouse.Name); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Touch)) From 20747664f4fc493ff04e59da02087ecc9aa211d0 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Thu, 17 Jul 2014 11:59:38 +0200 Subject: [PATCH 31/41] [KMS] VSync off causes a crash; force VSync on until fixed VSync off leads to buffer starvation and a crash in Egl.SwapBuffers. We need to understand why and fix that before we can disable vsync. --- Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs index 63122f71..f2d55186 100644 --- a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -72,7 +72,11 @@ namespace OpenTK.Platform.Linux if (is_flip_queued) { - WaitFlip(SwapInterval > 0); + // 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"); From 21bcc5eae19aebb34cba4f0284dc3da3ae1bebbf Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 09:02:01 +0200 Subject: [PATCH 32/41] [Linux] Print libinput seat for each detected device --- .../Platform/Linux/Bindings/LibInput.cs | 23 +++++++++++++ Source/OpenTK/Platform/Linux/LinuxInput.cs | 34 +++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index 15aa00db..97938410 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -82,6 +82,9 @@ namespace OpenTK.Platform.Linux } } + [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); @@ -115,6 +118,26 @@ namespace OpenTK.Platform.Linux [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*)SeatGetPhysicalName(seat)); + } + } } enum DeviceCapability diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index a79ec37c..b421b565 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -44,6 +44,8 @@ namespace OpenTK.Platform.Linux readonly IntPtr Device; string name; string output; + string logical_seat; + string physical_seat; public DeviceBase(IntPtr device, int id) { @@ -72,6 +74,32 @@ namespace OpenTK.Platform.Linux } } + 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 @@ -372,14 +400,16 @@ namespace OpenTK.Platform.Linux { KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); Keyboards.Add(keyboard.Id, keyboard); - Debug.Print("[Input] Added keyboard device {0} '{1}'", keyboard.Id, keyboard.Name); + 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); Mice.Add(mouse.Id, mouse); - Debug.Print("[Input] Added mouse device {0} '{1}'", mouse.Id, mouse.Name); + 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)) From 4aa2dcf5a3dacfa9a729fb98b8fcfbeef9f5ed50 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 09:05:46 +0200 Subject: [PATCH 33/41] [Linux] Fixed compilation issue --- Source/OpenTK/Platform/Linux/Bindings/LibInput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs index 97938410..8dd2b69d 100644 --- a/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs +++ b/Source/OpenTK/Platform/Linux/Bindings/LibInput.cs @@ -135,7 +135,7 @@ namespace OpenTK.Platform.Linux { unsafe { - return new string((sbyte*)SeatGetPhysicalName(seat)); + return new string((sbyte*)SeatGetPhysicalNameInternal(seat)); } } } From a30ec9003b544fc2dbbacdf4006cd57fca7ce2c4 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 09:19:37 +0200 Subject: [PATCH 34/41] [Linux] Fixed X11 vs KMS detection --- Source/OpenTK/Configuration.cs | 2 +- Source/OpenTK/Platform/Factory.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/Platform/Factory.cs b/Source/OpenTK/Platform/Factory.cs index 5cc0bfe1..2bcfada5 100644 --- a/Source/OpenTK/Platform/Factory.cs +++ b/Source/OpenTK/Platform/Factory.cs @@ -53,8 +53,8 @@ namespace OpenTK.Platform // Create regular platform backend if (Configuration.RunningOnSdl2) Default = new SDL2.Sdl2Factory(); - else if (Configuration.RunningOnLinux) Default = new Linux.LinuxFactory(); 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(); From b23d06eb46054e5265eeefbcde273bca1bbc9d5f Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 09:22:12 +0200 Subject: [PATCH 35/41] [Linux] Fixed CursorVisible=false behavior --- Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index 8391f53e..f3aebbe4 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -283,7 +283,7 @@ namespace OpenTK.Platform.Linux if (!CursorVisible) { x = MathHelper.Clamp(mouse.X, Bounds.Left, Bounds.Right - 1); - y = MathHelper.Clamp(mouse.X, Bounds.Top, Bounds.Bottom - 1); + y = MathHelper.Clamp(mouse.Y, Bounds.Top, Bounds.Bottom - 1); if (x != mouse.X || y != mouse.Y) { Mouse.SetPosition(x, y); From b1a31c08cc358b35e95bae58c379486b9166c3bf Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 09:44:47 +0200 Subject: [PATCH 36/41] [Examples] Only use WinForms when supported. --- .../OpenTK/Test/TestResolutionChanges.cs | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) 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 + { + } + } } } From 91055336c042a0f5f5d2ca63045d1f32b38ab6c1 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 09:45:04 +0200 Subject: [PATCH 37/41] [Linux] Fixed minor typo --- Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs index 928eac2c..d347a6aa 100644 --- a/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs +++ b/Source/OpenTK/Platform/Linux/LinuxDisplayDriver.cs @@ -259,7 +259,7 @@ namespace OpenTK.Platform.Linux unsafe static void GetModes(LinuxDisplay display, DisplayResolution[] modes, out DisplayResolution current) { int mode_count = display.pConnector->count_modes; - Debug.Print("[KMS] Display supports {0} modes", mode_count); + Debug.Print("[KMS] Display supports {0} mode(s)", mode_count); for (int i = 0; i < mode_count; i++) { ModeInfo* mode = display.pConnector->modes + i; From 51c05ea1a5ba2e9e315f09db925d6cf83babbd48 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 10:04:57 +0200 Subject: [PATCH 38/41] [KMS] Correctly restore crtc on exit. --- Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs index f2d55186..d6959544 100644 --- a/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs +++ b/Source/OpenTK/Platform/Linux/LinuxGraphicsContext.cs @@ -286,8 +286,8 @@ namespace OpenTK.Platform.Linux Drm.ModeSetCrtc(fd, wnd.DisplayDevice.pCrtc->crtc_id, wnd.DisplayDevice.pCrtc->buffer_id, - wnd.DisplayDevice.pCrtc->width, - wnd.DisplayDevice.pCrtc->height, + wnd.DisplayDevice.pCrtc->x, + wnd.DisplayDevice.pCrtc->y, &connector_id, 1, &mode); From a38e267156bb2f189229cf229c17fb291e15f074 Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 10:05:13 +0200 Subject: [PATCH 39/41] [Linux] Additional debugging info on shutdown --- Source/OpenTK/Platform/Linux/LinuxFactory.cs | 3 +++ Source/OpenTK/Platform/Linux/LinuxInput.cs | 6 ++++++ Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/Source/OpenTK/Platform/Linux/LinuxFactory.cs b/Source/OpenTK/Platform/Linux/LinuxFactory.cs index 23a3b2cb..21132c13 100644 --- a/Source/OpenTK/Platform/Linux/LinuxFactory.cs +++ b/Source/OpenTK/Platform/Linux/LinuxFactory.cs @@ -169,16 +169,19 @@ namespace OpenTK.Platform.Linux { 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); } diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index b421b565..c3f435f1 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -655,6 +655,7 @@ namespace OpenTK.Platform.Linux { if (input_context != IntPtr.Zero) { + Debug.Print("[Input] Destroying libinput context"); LibInput.Suspend(input_context); Interlocked.Increment(ref exit); @@ -664,12 +665,17 @@ namespace OpenTK.Platform.Linux 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() diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index f3aebbe4..a2a5660c 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -378,9 +378,14 @@ namespace OpenTK.Platform.Linux { 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 From 07d496d1817357ca2d12d28f7282ef9132cfc29f Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 10:13:45 +0200 Subject: [PATCH 40/41] [Linux] Fixed OnMouseMove being called without movement --- Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs index a2a5660c..961381ee 100644 --- a/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs +++ b/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs @@ -290,7 +290,7 @@ namespace OpenTK.Platform.Linux } } - if (X != previous_mouse.X || Y != previous_mouse.Y) + if (x != previous_mouse.X || y != previous_mouse.Y) { OnMouseMove(x, y); } From 00b5174f6348f3e86ee68512048ffb0e21b41b0c Mon Sep 17 00:00:00 2001 From: thefiddler Date: Fri, 18 Jul 2014 11:23:28 +0200 Subject: [PATCH 41/41] [Linux] Delay input device detection until a key is pressed --- Source/OpenTK/Platform/DeviceCollection.cs | 12 +++++- Source/OpenTK/Platform/Linux/LinuxInput.cs | 48 +++++++++++++++++----- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Source/OpenTK/Platform/DeviceCollection.cs b/Source/OpenTK/Platform/DeviceCollection.cs index 8229c291..0c70f356 100644 --- a/Source/OpenTK/Platform/DeviceCollection.cs +++ b/Source/OpenTK/Platform/DeviceCollection.cs @@ -77,14 +77,22 @@ namespace OpenTK.Platform public void Remove(int id) { - if (!Map.ContainsKey(id)) + if (!TryRemove(id)) { Debug.Print("Invalid DeviceCollection<{0}> id: {1}", typeof(T).FullName, id); - return; + } + } + + 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) diff --git a/Source/OpenTK/Platform/Linux/LinuxInput.cs b/Source/OpenTK/Platform/Linux/LinuxInput.cs index c3f435f1..ddca85f5 100644 --- a/Source/OpenTK/Platform/Linux/LinuxInput.cs +++ b/Source/OpenTK/Platform/Linux/LinuxInput.cs @@ -117,7 +117,6 @@ namespace OpenTK.Platform.Linux public KeyboardDevice(IntPtr device, int id) : base(device, id) { - State.SetIsConnected(true); } } @@ -128,13 +127,23 @@ namespace OpenTK.Platform.Linux public MouseDevice(IntPtr device, int id) : base(device, id) { - State.SetIsConnected(true); } } 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(); @@ -399,7 +408,7 @@ namespace OpenTK.Platform.Linux if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) { KeyboardDevice keyboard = new KeyboardDevice(device, Keyboards.Count); - Keyboards.Add(keyboard.Id, keyboard); + KeyboardCandidates.Add(keyboard.Id, keyboard); Debug.Print("[Input] Added keyboard device {0} '{1}' on '{2}' ('{3}')", keyboard.Id, keyboard.Name, keyboard.LogicalSeatName, keyboard.PhysicalSeatName); } @@ -407,7 +416,7 @@ namespace OpenTK.Platform.Linux if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) { MouseDevice mouse = new MouseDevice(device, Mice.Count); - Mice.Add(mouse.Id, mouse); + MouseCandidates.Add(mouse.Id, mouse); Debug.Print("[Input] Added mouse device {0} '{1}' on '{2}' ('{3}')", mouse.Id, mouse.Name, mouse.LogicalSeatName, mouse.PhysicalSeatName); } @@ -423,13 +432,15 @@ namespace OpenTK.Platform.Linux if (LibInput.DeviceHasCapability(device, DeviceCapability.Keyboard)) { int id = GetId(device); - Keyboards.Remove(id); + Keyboards.TryRemove(id); + KeyboardCandidates.TryRemove(id); } if (LibInput.DeviceHasCapability(device, DeviceCapability.Mouse)) { int id = GetId(device); - Mice.Remove(id); + Mice.TryRemove(id); + MouseCandidates.TryRemove(id); } } @@ -437,6 +448,9 @@ namespace OpenTK.Platform.Linux { 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) @@ -457,6 +471,8 @@ namespace OpenTK.Platform.Linux { if (mouse != null) { + mouse.State.SetIsConnected(true); + double value = e.AxisValue; PointerAxis axis = e.Axis; switch (axis) @@ -480,6 +496,8 @@ namespace OpenTK.Platform.Linux { if (mouse != null) { + mouse.State.SetIsConnected(true); + MouseButton button = Evdev.GetMouseButton(e.Button); ButtonState state = e.ButtonState; mouse.State[(MouseButton)button] = state == ButtonState.Pressed; @@ -491,6 +509,7 @@ namespace OpenTK.Platform.Linux Vector2 delta = new Vector2((float)e.X, (float)e.Y); if (mouse != null) { + mouse.State.SetIsConnected(true); mouse.State.Position += delta; } @@ -504,6 +523,7 @@ namespace OpenTK.Platform.Linux { if (mouse != null) { + mouse.State.SetIsConnected(true); mouse.State.Position = new Vector2(e.X, e.Y); } @@ -521,8 +541,12 @@ namespace OpenTK.Platform.Linux KeyboardDevice GetKeyboard(IntPtr device) { int id = GetId(device); - KeyboardDevice keyboard = Keyboards.FromHardwareId(id); - if (keyboard == null) + 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); } @@ -532,8 +556,12 @@ namespace OpenTK.Platform.Linux MouseDevice GetMouse(IntPtr device) { int id = GetId(device); - MouseDevice mouse = Mice.FromHardwareId(id); - if (mouse == null) + 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); }