#region License // // LinuxNativeWindow.cs // // Author: // Stefanos A. // // Copyright (c) 2006-2014 Stefanos Apostolopoulos // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #endregion using System; using System.Diagnostics; #if !MINIMAL using System.Drawing; #endif using System.Runtime.InteropServices; using OpenTK.Graphics; using OpenTK.Input; using OpenTK.Platform.Egl; namespace OpenTK.Platform.Linux { using Egl = OpenTK.Platform.Egl.Egl; class LinuxNativeWindow : NativeWindowBase { LinuxWindowInfo window; string title; Icon icon; Rectangle bounds; Size client_size; bool exists; bool is_focused; bool is_cursor_visible = true; KeyboardState previous_keyboard; MouseState previous_mouse; MouseCursor cursor_current; BufferObject cursor_custom; BufferObject cursor_default; BufferObject cursor_empty; public LinuxNativeWindow(IntPtr display, IntPtr gbm, int fd, int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice display_device) { Debug.Print("[KMS] Creating window on display {0:x}", display); Title = title; display_device = display_device ?? DisplayDevice.Default; if (display_device == null) { throw new NotSupportedException("[KMS] Driver does not currently support headless systems"); } window = new LinuxWindowInfo(display, fd, gbm, display_device.Id as LinuxDisplay); // Note: we only support fullscreen windows on KMS. // We implicitly override the requested width and height // by the width and height of the DisplayDevice, if any. width = display_device.Width; height = display_device.Height; bounds = new Rectangle(0, 0, width, height); client_size = bounds.Size; if (!mode.Index.HasValue) { mode = new EglGraphicsMode().SelectGraphicsMode(window, mode, 0); } Debug.Print("[KMS] Selected EGL mode {0}", mode); SurfaceFormat format = GetSurfaceFormat(display, mode); SurfaceFlags usage = SurfaceFlags.Rendering | SurfaceFlags.Scanout; if (!Gbm.IsFormatSupported(gbm, format, usage)) { Debug.Print("[KMS] Failed to find suitable surface format, using XRGB8888"); format = SurfaceFormat.XRGB8888; } Debug.Print("[KMS] Creating GBM surface on {0:x} with {1}x{2} {3} [{4}]", gbm, width, height, format, usage); IntPtr gbm_surface = Gbm.CreateSurface(gbm, width, height, format, usage); if (gbm_surface == IntPtr.Zero) { throw new NotSupportedException("[KMS] Failed to create GBM surface for rendering"); } window.Handle = gbm_surface; Debug.Print("[KMS] Created GBM surface {0:x}", window.Handle); window.CreateWindowSurface(mode.Index.Value); Debug.Print("[KMS] Created EGL surface {0:x}", window.Surface); cursor_default = CreateCursor(gbm, Cursors.Default); cursor_empty = CreateCursor(gbm, Cursors.Empty); Cursor = MouseCursor.Default; exists = true; } #region Private Members static BufferObject CreateCursor(IntPtr gbm, MouseCursor cursor) { if (cursor.Width > 64 || cursor.Height > 64) { Debug.Print("[KMS] Cursor size {0}x{1} unsupported. Maximum is 64x64.", cursor.Width, cursor.Height); return default(BufferObject); } int width = 64; int height = 64; SurfaceFormat format = SurfaceFormat.ARGB8888; SurfaceFlags usage = SurfaceFlags.Cursor64x64 | SurfaceFlags.Write; Debug.Print("[KMS] Gbm.CreateBuffer({0:X}, {1}, {2}, {3}, {4}).", gbm, width, height, format, usage); BufferObject bo = Gbm.CreateBuffer( gbm, width, height, format, usage); if (bo == BufferObject.Zero) { Debug.Print("[KMS] Failed to create buffer."); return bo; } // Copy cursor.Data into a new buffer of the correct size byte[] cursor_data = new byte[width * height * 4]; for (int y = 0; y < cursor.Height; y++) { int dst_offset = y * width * 4; int src_offset = y * cursor.Width * 4; int src_length = cursor.Width * 4; Array.Copy( cursor.Data, src_offset, cursor_data, dst_offset, src_length); } bo.Write(cursor_data); return bo; } void SetCursor(MouseCursor cursor) { BufferObject bo = default(BufferObject); if (cursor == MouseCursor.Default) { bo = cursor_default; } else if (cursor == MouseCursor.Empty) { bo = cursor_empty; } else { if (cursor_custom != BufferObject.Zero) cursor_custom.Dispose(); cursor_custom = CreateCursor(window.BufferManager, cursor); bo = cursor_custom; } // If we failed to create a proper cursor, try falling back // to the empty cursor. We do not want to crash here! if (bo == BufferObject.Zero) { bo = cursor_empty; } if (bo != BufferObject.Zero) { Drm.SetCursor(window.FD, window.DisplayDevice.Id, bo.Handle, bo.Width, bo.Height, cursor.X, cursor.Y); } } static SurfaceFormat GetSurfaceFormat(IntPtr display, GraphicsMode mode) { // Use EGL 1.4 EGL_NATIVE_VISUAL_ID to retrieve // the corresponding surface format. If that fails // fall back to a manual algorithm. int format; Egl.GetConfigAttrib(display, mode.Index.Value, Egl.NATIVE_VISUAL_ID, out format); if ((SurfaceFormat)format != 0) return (SurfaceFormat)format; Debug.Print("[KMS] Failed to retrieve EGL visual from GBM surface. Error: {0}", Egl.GetError()); Debug.Print("[KMS] Falling back to hardcoded formats."); int r = mode.ColorFormat.Red; int g = mode.ColorFormat.Green; int b = mode.ColorFormat.Blue; int a = mode.ColorFormat.Alpha; if (mode.ColorFormat.IsIndexed) return SurfaceFormat.C8; if (r == 3 && g == 3 && b == 2 && a == 0) return SurfaceFormat.RGB332; if (r == 5 && g == 6 && b == 5 && a == 0) return SurfaceFormat.RGB565; if (r == 5 && g == 6 && b == 5 && a == 0) return SurfaceFormat.RGB565; if (r == 8 && g == 8 && b == 8 && a == 0) return SurfaceFormat.RGB888; if (r == 5 && g == 5 && b == 5 && a == 1) return SurfaceFormat.RGBA5551; if (r == 10 && g == 10 && b == 10 && a == 2) return SurfaceFormat.RGBA1010102; if (r == 4 && g == 4 && b == 4 && a == 4) return SurfaceFormat.RGBA4444; if (r == 8 && g == 8 && b == 8 && a == 8) return SurfaceFormat.RGBA8888; return SurfaceFormat.RGBA8888; } KeyboardState ProcessKeyboard(KeyboardState keyboard) { for (Key i = 0; i < Key.LastKey; i++) { if (keyboard[i]) { OnKeyDown(i, previous_keyboard[i]); // Todo: implement libxkb-common binding for text input } if (!keyboard[i] && previous_keyboard[i]) { OnKeyUp(i); } } return keyboard; } MouseState ProcessMouse(MouseState mouse) { // Handle mouse buttons for (MouseButton i = 0; i < MouseButton.LastButton; i++) { if (mouse[i] && !previous_mouse[i]) { OnMouseDown(i); } if (!mouse[i] && previous_mouse[i]) { OnMouseUp(i); } } // Handle mouse movement { int x = mouse.X; int y = mouse.Y; // Make sure the mouse cannot leave the GameWindow when captured if (!CursorVisible) { x = MathHelper.Clamp(mouse.X, Bounds.Left, Bounds.Right - 1); y = MathHelper.Clamp(mouse.Y, Bounds.Top, Bounds.Bottom - 1); if (x != mouse.X || y != mouse.Y) { Mouse.SetPosition(x, y); } } if (x != previous_mouse.X || y != previous_mouse.Y) { OnMouseMove(x, y); } } // Handle mouse scroll if (mouse.Scroll != previous_mouse.Scroll) { float dx = mouse.Scroll.X - previous_mouse.Scroll.X; float dy = mouse.Scroll.Y - previous_mouse.Scroll.Y; OnMouseWheel(dx, dy); } // Handle mouse focus // Note: focus follows mouse. Literally. bool cursor_in = Bounds.Contains(new Point(mouse.X, mouse.Y)); if (!cursor_in && Focused) { OnMouseLeave(EventArgs.Empty); SetFocus(false); } else if (cursor_in && !Focused) { OnMouseEnter(EventArgs.Empty); SetFocus(true); } return mouse; } void SetFocus(bool focus) { if (is_focused != focus) { is_focused = focus; OnFocusedChanged(EventArgs.Empty); } } #endregion #region INativeWindow Members public override void ProcessEvents() { // Note: there is no event-based keyboard/mouse input available. // We will fake that by polling OpenTK.Input. previous_keyboard = ProcessKeyboard(Keyboard.GetState()); previous_mouse = ProcessMouse(Mouse.GetCursorState()); base.ProcessEvents(); } public override void Close() { exists = false; } public override Point PointToClient(Point point) { var origin = Point.Empty; var display = DisplayDevice.Default; if (display != null) { origin = display.Bounds.Location; } var client = Location; return new Point(point.X + client.X - origin.X, point.Y + client.Y - origin.Y); } public override Point PointToScreen(Point point) { var origin = Point.Empty; var display = DisplayDevice.Default; if (display != null) { origin = display.Bounds.Location; } var client = Location; return new Point(point.X + origin.X - client.X, point.Y + origin.Y - client.Y); } protected override void Dispose(bool disposing) { if (disposing) { Debug.Print("[KMS] Destroying window {0}.", window.Handle); window.Dispose(); Gbm.DestroySurface(window.Handle); } else { Debug.Print("[KMS] {0} leaked. Did you forget to call Dispose()?", GetType().FullName); } } public override Icon Icon { get { return icon; } set { if (icon != value) { icon = value; OnIconChanged(EventArgs.Empty); } } } public override string Title { get { return title; } set { if (title != value) { title = value; OnTitleChanged(EventArgs.Empty); } } } public override bool Focused { get { return is_focused; } } public override bool Visible { get { return true; } set { } } public override bool Exists { get { return exists; } } public override IWindowInfo WindowInfo { get { return window; } } public override WindowState WindowState { get { return WindowState.Fullscreen; } set { } } public override WindowBorder WindowBorder { get { return WindowBorder.Hidden; } set { } } public override Rectangle Bounds { get { return bounds; } set { } } public override Size ClientSize { get { return client_size; } set { } } public override bool CursorVisible { get { return is_cursor_visible; } set { if (value && !is_cursor_visible) { SetCursor(cursor_current); } else if (!value && is_cursor_visible) { SetCursor(MouseCursor.Empty); } is_cursor_visible = value; } } public override MouseCursor Cursor { get { return cursor_current; } set { if (cursor_current != value) { if (cursor_custom != BufferObject.Zero) { cursor_custom.Dispose(); } if (CursorVisible) { SetCursor(value); } cursor_current = value; } } } #endregion } }