Opentk/Source/OpenTK/Platform/Linux/LinuxNativeWindow.cs
2014-07-18 09:22:12 +02:00

545 lines
16 KiB
C#

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