Opentk/Source/OpenTK/Platform/X11/X11GLNative.cs

885 lines
30 KiB
C#

#region --- License ---
/* Licensed under the MIT/X11 license.
* Copyright (c) 2006-2008 the OpenTK Team.
* This notice may not be removed from any source distribution.
* See license.txt for licensing detailed licensing details.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using OpenTK.Platform.Windows;
using OpenTK.Graphics;
//using OpenTK.Graphics.OpenGL;
namespace OpenTK.Platform.X11
{
/// <summary>
/// Drives GameWindow on X11.
/// This class supports OpenTK, and is not intended for use by OpenTK programs.
/// </summary>
internal sealed class X11GLNative : INativeGLWindow, IDisposable
{
// TODO: Disable screensaver.
// TODO: What happens if we can't disable decorations through motif?
// TODO: Mouse/keyboard grabbing/wrapping.
// TODO: PointToWindow, PointToScreen
#region --- Fields ---
X11WindowInfo window = new X11WindowInfo();
X11Input driver;
// Window manager hints for fullscreen windows.
const string MOTIF_WM_ATOM = "_MOTIF_WM_HINTS";
const string KDE_WM_ATOM = "KWM_WIN_DECORATION";
const string KDE_NET_WM_ATOM = "_KDE_NET_WM_WINDOW_TYPE";
const string ICCM_WM_ATOM = "_NET_WM_WINDOW_TYPE";
// Number of pending events.
int pending = 0;
int width, height;
int top, bottom, left, right;
// C# ResizeEventArgs
ResizeEventArgs resizeEventArgs = new ResizeEventArgs();
// Used for event loop.
XEvent e = new XEvent();
bool disposed;
bool exists;
bool isExiting;
// XAtoms for window properties
static IntPtr WMTitle; // The title of the GameWindow.
static IntPtr UTF8String; // No idea.
// Fields used for fullscreen mode changes.
int pre_fullscreen_width, pre_fullscreen_height;
bool fullscreen = false;
#endregion
#region --- Constructors ---
/// <summary>
/// Constructs and initializes a new X11GLNative window.
/// Call CreateWindow to create the actual render window.
/// </summary>
public X11GLNative()
{
try
{
Debug.Print("Creating X11GLNative window.");
Debug.Indent();
//Utilities.ThrowOnX11Error = true; // Not very reliable
// We *cannot* reuse the display connection of System.Windows.Forms (events get mixed with Windows.Forms).
// TODO: Multiple screens.
//Type xplatui = Type.GetType("System.Windows.Forms.XplatUIX11, System.Windows.Forms");
//window.Display = (IntPtr)xplatui.GetField("DisplayHandle",
// System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(null);
//window.RootWindow = (IntPtr)xplatui.GetField("RootWindow",
// System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(null);
//window.Screen = (int)xplatui.GetField("ScreenNo",
// System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).GetValue(null);
// Open a display connection to the X server, and obtain the screen and root window.
window.Display = API.DefaultDisplay;//Functions.XOpenDisplay(IntPtr.Zero); // IntPtr.Zero == default display
if (window.Display == IntPtr.Zero)
throw new Exception("Could not open connection to X");
try
{
Functions.XLockDisplay(window.Display);
window.Screen = Functions.XDefaultScreen(window.Display); //API.DefaultScreen;
window.RootWindow = Functions.XRootWindow(window.Display, window.Screen); // API.RootWindow;
}
finally
{
Functions.XUnlockDisplay(window.Display);
}
Debug.Print("Display: {0}, Screen {1}, Root window: {2}", window.Display, window.Screen, window.RootWindow);
RegisterAtoms(window);
}
finally
{
Debug.Unindent();
}
}
#endregion
#region private static void RegisterAtoms()
/// <summary>
/// Not used yet.
/// Registers the necessary atoms for GameWindow.
/// </summary>
private static void RegisterAtoms(X11WindowInfo window)
{
string[] atom_names = new string[]
{
"WM_TITLE",
"UTF8_STRING"
};
IntPtr[] atoms = new IntPtr[atom_names.Length];
//Functions.XInternAtoms(window.Display, atom_names, atom_names.Length, false, atoms);
int offset = 0;
WMTitle = atoms[offset++];
UTF8String = atoms[offset++];
}
#endregion
#region --- INativeGLWindow Members ---
#region public void CreateWindow(int width, int height, GraphicsMode mode, out IGraphicsContext context)
public void CreateWindow(int width, int height, GraphicsMode mode, out IGraphicsContext context)
{
if (width <= 0) throw new ArgumentOutOfRangeException("width", "Must be higher than zero.");
if (height <= 0) throw new ArgumentOutOfRangeException("height", "Must be higher than zero.");
if (exists) throw new InvalidOperationException("A render window already exists.");
XVisualInfo info = new XVisualInfo();
Debug.Indent();
lock (API.Lock)
{
info.visualid = mode.Index;
int dummy;
window.VisualInfo = (XVisualInfo)Marshal.PtrToStructure(
Functions.XGetVisualInfo(window.Display, XVisualInfoMask.ID, ref info, out dummy), typeof(XVisualInfo));
// Create a window on this display using the visual above
Debug.Write("Opening render window... ");
XSetWindowAttributes attributes = new XSetWindowAttributes();
attributes.background_pixel = IntPtr.Zero;
attributes.border_pixel = IntPtr.Zero;
attributes.colormap = Functions.XCreateColormap(window.Display, window.RootWindow, window.VisualInfo.visual, 0/*AllocNone*/);
window.EventMask = EventMask.StructureNotifyMask | EventMask.SubstructureNotifyMask | EventMask.ExposureMask |
EventMask.KeyReleaseMask | EventMask.KeyPressMask |
EventMask.PointerMotionMask | // Bad! EventMask.PointerMotionHintMask |
EventMask.ButtonPressMask | EventMask.ButtonReleaseMask;
attributes.event_mask = (IntPtr)window.EventMask;
uint mask = (uint)SetWindowValuemask.ColorMap | (uint)SetWindowValuemask.EventMask |
(uint)SetWindowValuemask.BackPixel | (uint)SetWindowValuemask.BorderPixel;
window.WindowHandle = Functions.XCreateWindow(window.Display, window.RootWindow,
0, 0, width, height, 0, window.VisualInfo.depth/*(int)CreateWindowArgs.CopyFromParent*/,
(int)CreateWindowArgs.InputOutput, window.VisualInfo.visual, (UIntPtr)mask, ref attributes);
if (window.WindowHandle == IntPtr.Zero)
throw new ApplicationException("XCreateWindow call failed (returned 0).");
//XVisualInfo vis = window.VisualInfo;
//Glx.CreateContext(window.Display, ref vis, IntPtr.Zero, true);
}
context = new GraphicsContext(mode, window);
// Set the window hints
XSizeHints hints = new XSizeHints();
hints.x = 0;
hints.y = 0;
hints.width = width;
hints.height = height;
hints.flags = (IntPtr)(XSizeHintsFlags.USSize | XSizeHintsFlags.USPosition);
lock (API.Lock)
{
Functions.XSetWMNormalHints(window.Display, window.WindowHandle, ref hints);
// Register for window destroy notification
IntPtr wm_destroy_atom = Functions.XInternAtom(window.Display, "WM_DELETE_WINDOW", true);
Functions.XSetWMProtocols(window.Display, window.WindowHandle, new IntPtr[] { wm_destroy_atom }, 1);
}
Top = Left = 0;
Right = Width;
Bottom = Height;
//XTextProperty text = new XTextProperty();
//text.value = "OpenTK Game Window";
//text.format = 8;
//Functions.XSetWMName(window.Display, window.Handle, ref text);
//Functions.XSetWMProperties(display, window, name, name, 0, /*None*/ null, 0, hints);
Debug.Print("done! (id: {0})", window.WindowHandle);
lock (API.Lock)
{
API.MapRaised(window.Display, window.WindowHandle);
}
mapped = true;
//context.CreateContext(true, null);
driver = new X11Input(window);
Debug.WriteLine("X11GLNative window created successfully!");
Debug.Unindent();
exists = true;
}
#endregion
#region public void ProcessEvents()
public void ProcessEvents()
{
// Process all pending events
//while (true)
while (Functions.XCheckWindowEvent(window.Display, window.WindowHandle, window.EventMask, ref e) ||
Functions.XCheckTypedWindowEvent(window.Display, window.WindowHandle, XEventName.ClientMessage, ref e))
{
//pending = Functions.XPending(window.Display);
//pending = API.Pending(window.Display);
//if (pending == 0)
// return;
//Functions.XNextEvent(window.Display, ref e);
//Debug.Print("Event: {0} ({1} pending)", e.type, pending);
// Respond to the event e
switch (e.type)
{
case XEventName.MapNotify:
Debug.WriteLine("Window mapped.");
return;
case XEventName.CreateNotify:
// A child was was created - nothing to do
break;
case XEventName.ClientMessage:
this.OnDestroy(EventArgs.Empty);
break;
case XEventName.DestroyNotify:
exists = false;
isExiting = true;
Debug.Print("X11 window {0} destroyed.", e.DestroyWindowEvent.window);
window.WindowHandle = IntPtr.Zero;
return;
case XEventName.ConfigureNotify:
// If the window size changed, raise the C# Resize event.
if (e.ConfigureEvent.width != width || e.ConfigureEvent.height != height)
{
Debug.WriteLine(String.Format("ConfigureNotify: {0}x{1}", e.ConfigureEvent.width, e.ConfigureEvent.height));
resizeEventArgs.Width = e.ConfigureEvent.width;
resizeEventArgs.Height = e.ConfigureEvent.height;
this.OnResize(resizeEventArgs);
}
break;
case XEventName.KeyPress:
case XEventName.KeyRelease:
case XEventName.MotionNotify:
case XEventName.ButtonPress:
case XEventName.ButtonRelease:
//Functions.XPutBackEvent(window.Display, ref e);
driver.ProcessEvent(ref e);
break;
default:
Debug.WriteLine(String.Format("{0} event was not handled", e.type));
break;
}
}
}
#endregion
#region public IInputDriver InputDriver
public IInputDriver InputDriver
{
get
{
return driver;
}
}
#endregion
#region public bool Exists
/// <summary>
/// Returns true if a render window/context exists.
/// </summary>
public bool Exists
{
get { return exists; }
}
#endregion
#region public bool Quit
public bool IsExiting
{
get { return isExiting; }
}
#endregion
#region public bool IsIdle
public bool IsIdle
{
get { throw new Exception("The method or operation is not implemented."); }
}
#endregion
#region public bool Fullscreen
public bool Fullscreen
{
get
{
return fullscreen;
}
set
{
if (value && !fullscreen)
{
Debug.Print("Going fullscreen");
Debug.Indent();
DisableWindowDecorations();
pre_fullscreen_height = this.Height;
pre_fullscreen_width = this.Width;
//Functions.XRaiseWindow(this.window.Display, this.Handle);
Functions.XMoveResizeWindow(this.window.Display, this.Handle, 0, 0,
DisplayDevice.Default.Width, DisplayDevice.Default.Height);
Debug.Unindent();
fullscreen = true;
}
else if (!value && fullscreen)
{
Debug.Print("Going windowed");
Debug.Indent();
Functions.XMoveResizeWindow(this.window.Display, this.Handle, 0, 0,
pre_fullscreen_width, pre_fullscreen_height);
pre_fullscreen_height = pre_fullscreen_width = 0;
EnableWindowDecorations();
Debug.Unindent();
fullscreen = false;
}
/*
Debug.Print(value ? "Going fullscreen" : "Going windowed");
IntPtr state_atom = Functions.XInternAtom(this.window.Display, "_NET_WM_STATE", false);
IntPtr fullscreen_atom = Functions.XInternAtom(this.window.Display, "_NET_WM_STATE_FULLSCREEN", false);
XEvent xev = new XEvent();
xev.ClientMessageEvent.type = XEventName.ClientMessage;
xev.ClientMessageEvent.serial = IntPtr.Zero;
xev.ClientMessageEvent.send_event = true;
xev.ClientMessageEvent.window = this.Handle;
xev.ClientMessageEvent.message_type = state_atom;
xev.ClientMessageEvent.format = 32;
xev.ClientMessageEvent.ptr1 = (IntPtr)(value ? NetWindowManagerState.Add : NetWindowManagerState.Remove);
xev.ClientMessageEvent.ptr2 = (IntPtr)(value ? 1 : 0);
xev.ClientMessageEvent.ptr3 = IntPtr.Zero;
Functions.XSendEvent(this.window.Display, API.RootWindow, false,
(IntPtr)(EventMask.SubstructureRedirectMask | EventMask.SubstructureNotifyMask), ref xev);
fullscreen = !fullscreen;
*/
}
}
#endregion
#region public IntPtr Handle
/// <summary>
/// Gets the current window handle.
/// </summary>
public IntPtr Handle
{
get { return this.window.WindowHandle; }
}
#endregion
#region public string Title
/// <summary>
/// TODO: Use atoms for this property.
/// Gets or sets the GameWindow title.
/// </summary>
public string Title
{
get
{
IntPtr name = IntPtr.Zero;
Functions.XFetchName(window.Display, window.WindowHandle, ref name);
if (name != IntPtr.Zero)
return Marshal.PtrToStringAnsi(name);
return String.Empty;
}
set
{
/*
XTextProperty name = new XTextProperty();
name.format = 8; //STRING
if (value == null)
name.value = String.Empty;
else
name.value = value;
Functions.XSetWMName(window.Display, window.Handle, ref name);
*/
if (value != null)
Functions.XStoreName(window.Display, window.WindowHandle, value);
}
}
#endregion
#region public bool Visible
bool mapped;
public bool Visible
{
get
{
//return true;
return mapped;
}
set
{
if (value && !mapped)
{
Functions.XMapWindow(window.Display, window.WindowHandle);
mapped = true;
}
else if (!value && mapped)
{
Functions.XUnmapWindow(window.Display, window.WindowHandle);
mapped = false;
}
}
}
#endregion
#region public IWindowInfo WindowInfo
public IWindowInfo WindowInfo
{
get { return window; }
}
#endregion
#region OnCreate
public event CreateEvent Create;
private void OnCreate(EventArgs e)
{
if (this.Create != null)
{
Debug.Print("Create event fired from window: {0}", window.ToString());
this.Create(this, e);
}
}
#endregion
#region public void Exit()
public void Exit()
{
this.DestroyWindow();
}
#endregion
#region public void DestroyWindow()
public void DestroyWindow()
{
Debug.WriteLine("X11GLNative shutdown sequence initiated.");
Functions.XDestroyWindow(window.Display, window.WindowHandle);
}
#endregion
#region OnDestroy
public event DestroyEvent Destroy;
private void OnDestroy(EventArgs e)
{
Debug.Print("Destroy event fired from window: {0}", window.ToString());
if (this.Destroy != null)
this.Destroy(this, e);
}
#endregion
#region PointToClient
public void PointToClient(ref System.Drawing.Point p)
{
/*
if (!Functions.ScreenToClient(this.Handle, p))
throw new InvalidOperationException(String.Format(
"Could not convert point {0} from client to screen coordinates. Windows error: {1}",
p.ToString(), Marshal.GetLastWin32Error()));
*/
}
#endregion
#region PointToScreen
public void PointToScreen(ref System.Drawing.Point p)
{
throw new NotImplementedException();
}
#endregion
#endregion
#region --- IResizable Members ---
#region public int Width
public int Width
{
get
{
return width;
}
set
{/*
// Clear event struct
//Array.Clear(xresize.pad, 0, xresize.pad.Length);
// Set requested parameters
xresize.ResizeRequest.type = EventType.ResizeRequest;
xresize.ResizeRequest.display = this.display;
xresize.ResizeRequest.width = value;
xresize.ResizeRequest.height = mode.Width;
API.SendEvent(
this.display,
this.window,
false,
EventMask.StructureNotifyMask,
ref xresize
);*/
}
}
#endregion
#region public int Height
public int Height
{
get
{
return height;
}
set
{/*
// Clear event struct
//Array.Clear(xresize.pad, 0, xresize.pad.Length);
// Set requested parameters
xresize.ResizeRequest.type = EventType.ResizeRequest;
xresize.ResizeRequest.display = this.display;
xresize.ResizeRequest.width = mode.Width;
xresize.ResizeRequest.height = value;
API.SendEvent(
this.display,
this.window,
false,
EventMask.StructureNotifyMask,
ref xresize
);*/
}
}
#endregion
#region public event ResizeEvent Resize
public event ResizeEvent Resize;
private void OnResize(ResizeEventArgs e)
{
width = e.Width;
height = e.Height;
if (this.Resize != null)
{
this.Resize(this, e);
}
}
#endregion
public int Top
{
get { return top; }
private set { top = value; }
}
public int Bottom
{
get { return bottom; }
private set { bottom = value; }
}
public int Left
{
get { return left; }
private set { left = value; }
}
public int Right
{
get { return right; }
private set { right = value; }
}
#endregion
#region --- IDisposable Members ---
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool manuallyCalled)
{
if (!disposed)
{
if (window != null && window.WindowHandle != IntPtr.Zero)
{
try
{
Functions.XLockDisplay(window.Display);
Functions.XDestroyWindow(window.Display, window.WindowHandle);
}
finally
{
Functions.XUnlockDisplay(window.Display);
}
window = null;
}
if (manuallyCalled)
{
}
disposed = true;
}
}
~X11GLNative()
{
this.Dispose(false);
}
#endregion
#region --- Private Methods ---
#region void DisableWindowDecorations()
void DisableWindowDecorations()
{
bool removed = false;
if (DisableMotifDecorations()) { Debug.Print("Removed decorations through motif."); removed = true; }
if (DisableGnomeDecorations()) { Debug.Print("Removed decorations through gnome."); removed = true; }
if (DisableIccmDecorations()) { Debug.Print("Removed decorations through ICCM."); removed = true; }
if (removed)
{
Functions.XSetTransientForHint(this.window.Display, this.Handle, this.window.RootWindow);
Functions.XUnmapWindow(this.window.Display, this.Handle);
Functions.XMapWindow(this.window.Display, this.Handle);
}
}
#region bool DisableMotifDecorations()
bool DisableMotifDecorations()
{
IntPtr atom = Functions.XInternAtom(this.window.Display, MOTIF_WM_ATOM, true);
if (atom != IntPtr.Zero)
{
MotifWmHints hints = new MotifWmHints();
hints.flags = (IntPtr)MotifFlags.Decorations;
Functions.XChangeProperty(this.window.Display, this.Handle, atom, atom, 32, PropertyMode.Replace, ref hints, 5
/*Marshal.SizeOf(hints) / 4*/);
return true;
}
return false;
}
#endregion
#region bool DisableGnomeDecorations()
bool DisableGnomeDecorations()
{
// Attempt to cover gnome panels.
//XEvent xev = new XEvent();
//xev.ClientMessageEvent.window = this.window.Handle;
//xev.ClientMessageEvent.type = XEventName.ClientMessage;
//xev.ClientMessageEvent.message_type = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_LAYER, false);
//xev.ClientMessageEvent.format = 32;
//xev.ClientMessageEvent.ptr1 = (IntPtr)WindowLayer.AboveDock;
//Functions.XSendEvent(this.window.Display, this.window.RootWindow, false, (IntPtr)EventMask.SubstructureNotifyMask, ref xev);
//xev = new XEvent();
//xev.ClientMessageEvent.window = this.window.Handle;
//xev.ClientMessageEvent.type = XEventName.ClientMessage;
//xev.ClientMessageEvent.message_type = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_STATE, false);
//xev.ClientMessageEvent.format = 32;
//xev.ClientMessageEvent.ptr1 = (IntPtr)WindowState.;
//xev.ClientMessageEvent.ptr2 = (IntPtr)WindowLayer.AboveDock;
//Functions.XSendEvent(this.window.Display, this.window.RootWindow, false, (IntPtr)EventMask.SubstructureNotifyMask, ref xev);
IntPtr atom = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_HINTS, true);
if (atom != IntPtr.Zero)
{
IntPtr hints = IntPtr.Zero;
Functions.XChangeProperty(this.window.Display, this.Handle, atom, atom, 32, PropertyMode.Replace, ref hints,
/*Marshal.SizeOf(hints) / 4*/ 1);
return true;
}
return false;
}
#endregion
#region bool DisableIccmDecorations()
bool DisableIccmDecorations()
{
IntPtr atom = Functions.XInternAtom(this.window.Display, ICCM_WM_ATOM, true);
if (atom != IntPtr.Zero)
{
IntPtr hints = Functions.XInternAtom(this.window.Display, "_NET_WM_STATE_FULLSCREEN", true);
Functions.XChangeProperty(this.window.Display, this.Handle, atom, atom, 32, PropertyMode.Replace, ref hints, 1
/*Marshal.SizeOf(hints) / 4*/);
return true;
}
return false;
}
#endregion
#endregion
#region void EnableWindowDecorations()
void EnableWindowDecorations()
{
bool activated = false;
if (EnableMotifDecorations()) { Debug.Print("Activated decorations through motif."); activated = true; }
if (EnableGnomeDecorations()) { Debug.Print("Activated decorations through gnome."); activated = true; }
if (EnableIccmDecorations()) { Debug.Print("Activated decorations through ICCM."); activated = true; }
if (activated)
{
Functions.XSetTransientForHint(this.window.Display, this.Handle, this.window.RootWindow);
Functions.XUnmapWindow(this.window.Display, this.Handle);
Functions.XMapWindow(this.window.Display, this.Handle);
}
}
#region bool EnableMotifDecorations()
bool EnableMotifDecorations()
{
IntPtr atom = Functions.XInternAtom(this.window.Display, MOTIF_WM_ATOM, true);
if (atom != IntPtr.Zero)
{
Functions.XDeleteProperty(this.window.Display, this.Handle, atom);
return true;
}
return false;
}
#endregion
#region bool EnableGnomeDecorations()
bool EnableGnomeDecorations()
{
// Restore window layer.
//XEvent xev = new XEvent();
//xev.ClientMessageEvent.window = this.window.Handle;
//xev.ClientMessageEvent.type = XEventName.ClientMessage;
//xev.ClientMessageEvent.message_type = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_LAYER, false);
//xev.ClientMessageEvent.format = 32;
//xev.ClientMessageEvent.ptr1 = (IntPtr)WindowLayer.AboveDock;
//Functions.XSendEvent(this.window.Display, this.window.RootWindow, false, (IntPtr)EventMask.SubstructureNotifyMask, ref xev);
IntPtr atom = Functions.XInternAtom(this.window.Display, Constants.XA_WIN_HINTS, true);
if (atom != IntPtr.Zero)
{
Functions.XDeleteProperty(this.window.Display, this.Handle, atom);
return true;
}
return false;
}
#endregion
#region bool EnableIccmDecorations()
bool EnableIccmDecorations()
{
IntPtr atom = Functions.XInternAtom(this.window.Display, ICCM_WM_ATOM, true);
if (atom != IntPtr.Zero)
{
IntPtr hint = Functions.XInternAtom(this.window.Display, "_NET_WM_WINDOW_TYPE_NORMAL", true);
if (hint != IntPtr.Zero)
{
Functions.XChangeProperty(this.window.Display, this.Handle, hint, /*XA_ATOM*/(IntPtr)4, 32, PropertyMode.Replace,
ref hint, Marshal.SizeOf(hint) / 4);
}
return true;
}
return false;
}
#endregion
#endregion
#endregion
}
}